2

I try to learn how to use Thread Pool and Mutex, as a practice I'm trying to make an application that copy files from one path in the computer to another path in the computer. To do this application I used Thread Pool (so a few copies could happen simultaneously):

object[] paths = new object [2];  // The source path and the destination path
string[] files[] = System.IO.Directory.GetFiles(sourceFolderPath); //string sourceFolderPath = Folder path that contains the files
foreach(string s in files)
{
    paths[0] = s; // The file source - s = the file name with the file path;
    paths[1] = s.Replace(sourceFolderPath, destFolderPath); // Replaces between the source folder and the destination folder but keeps the file name
    ThreadPool.QueueUserWorkItem(new waitCallback(CopyFIle), paths);  
}

So far, the application sends each one of the files to the function that copy the file from the source folder to the destination folder.

The CopyFile function looks like this:

static void CopyFiles(object paths)
{
    object[] temp = paths as object[]; // The casting to object array
    string sourcePath = temp[0].ToString();    
    string destPath = temp[1].ToString();
    System.IO.File.Copy(filePath, destPath, true); // The  copy from the source to the dest
}

The weird thing is that when I run the application it throws an exception: "The process cannot access to the file 'C:..........' because it is being used by another process". When I try to find out the mistake and I run the application step by step the application run properly and the whole files are copied from the source folder to the destination folder.

That case made me think that maybe the fact that I'm using ThreadPool made a two threads or more to open the same file (the thing that should not happen because I used foreach and each one of the files paths is transferred as a parameter only once).

To solve this I tried to use Mutex and now the CopyFile function looks like this:

static Mutex mutex = new Mutex();
static void CopyFiles(object paths)
{
    Mutex.WaitOne();  //Waits until the critical section is free from other threads
    try
    {
         object[] temp = paths as object[]; // The casting to object array
         string sourcePath = temp[0].ToString();    
         string destPath = temp[1].ToString();
         System.IO.File.Copy(filePath, destPath, true); // The  copy from the source to the dest
    }
    Finally
    {
         Mutex.ReleaseMutex(); //Release the critical area
    }
}

Now the application should wait until the critical area is free and just then try to copy the file so the exception: "The process cannot access to the file 'C:..........' because it is being used by another process" should not appear. As I thought, the exception did not appear but the application copied only one file from the source folder to the destination folder and not all of the files. When I tried to run this application step by step the same weird thing happened and everything went properly, all of the files were copied to the destination folder.

Why this is happen? And how can I solve this problem so all the files will be copied to the destination folder in a normal application running and not step by step?

ShaqD
  • 63
  • 1
  • 10
  • Try logging (or writing to console) what is going on in CopyFiles() – GinjaNinja Jul 29 '15 at 09:31
  • I'm pretty sure he will see in the last calls of `CopyFiles` that the same string-values are used, because the unboxing for the second to last call will be done after the last call to the `ThreadPool` was made. – Patrik Jul 29 '15 at 09:40
  • Maybe the topic [passing by reference vs. passing by value](http://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value) is interesting here for you. – Patrik Jul 29 '15 at 09:42

1 Answers1

2

Your problem isn't the ThreadPool. What goes wrong is the argument.

In your first code-snippet, you fill an object-array with two argument-values and pass that to the Queue-method. What happens here is, that you use always the same array. So in the first iteration of your foreach-loop, you write two values into the array, pass it. Eventually, the method gets executed be the ThreadPool, using that object-array. In the same time, in the second iteration of the foreach-loop, you write again into that exact array and pass it again to the ThreadPool. That means, two (or more) Threads are starting to work on that array.

You don't know when the CopyFiles-method is active, so you can't tell when the array was unboxed and is ready for re-usage. That could be achieved using a mutual exclusion (the easiest way in C# is using the lock-keyword), but that's not the way you should use here.

The better way would be to create new object-arrays each iteration of the foreach-loop. Simply change the code to:

string[] files[] = System.IO.Directory.GetFiles(sourceFolderPath); //string sourceFolderPath = Folder path that contains the files
foreach(string s in files)
{
    object[] paths = new object [2];  // The source path and the destination path
    paths[0] = s; // The file source - s = the file name with the file path;
    paths[1] = s.Replace(sourceFolderPath, destFolderPath); // Replaces between the source folder and the destination folder but keeps the file name
    ThreadPool.QueueUserWorkItem(new waitCallback(CopyFIle), paths);  
}
Patrik
  • 1,355
  • 12
  • 22
  • First of all thanks, what you said fixed the problem, but another problem appeared. Not all of the files are copied all of the time, if I run the application a lot of times just in a few runs the program will copy the whole files in the source folder. For example: if I run the application 10 times and the source folder has 50 files so in the first time only 35 files were copied, in the second time all of the files were copied, in the third time just 10 files were copied, in the fourth time all of the files were copied, in the fifth time all of the files were copied and etc... – ShaqD Jul 29 '15 at 14:33
  • That is actually enough for a whole new question ^^ The problem (probably) that your main-thread ends. The ThreadPool Threads are Background-Threads (don't think of changing that). A Background-Thread is terminated when all Foreground-Threads are done. You might use Tasks instead of the ThreadPool or use another mechanism to ensure, that all your worker-Threads are finished (Semaphore, simple Counter, etc.) – Patrik Jul 30 '15 at 07:23
  • I ensured that the working threads were finished and it solved the problem, thanks a lot!! – ShaqD Jul 30 '15 at 07:44