我尝试学习如何使用线程池和互斥锁,作为一种实践,我试图使应用程序将文件从计算机中的一个路径复制到计算机中的另一个路径。为了完成这个应用程序,我使用了线程池(因此可以同时进行几个副本):
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);
}
到目前为止,应用程序将每个文件发送给将文件从源文件夹复制到目标文件夹的函数。
CopyFile函数如下所示:
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
}
奇怪的是,当我运行应用程序时,它抛出了一个异常:"进程不能访问文件'C:.........."因为它正在被另一个进程使用"。当我试图找出错误,我一步一步地运行应用程序,应用程序正常运行,整个文件从源文件夹复制到目标文件夹。
这种情况让我认为,也许我使用ThreadPool做了两个或更多的线程来打开同一个文件的事实(不应该发生的事情,因为我使用foreach和每个文件路径只作为参数传输一次)。
为了解决这个问题,我尝试使用互斥锁,现在CopyFile函数看起来像这样:
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
}
}
现在应用程序应该等待,直到关键区域空闲,然后尝试复制文件,因此异常:"进程无法访问文件'C:.........."因为它正在被另一个进程使用"不应该出现。正如我所想的那样,没有出现异常,但应用程序只将一个文件从源文件夹复制到目标文件夹,而不是所有文件。当我试图一步一步地运行这个应用程序时,同样奇怪的事情发生了,一切都很正常,所有的文件都被复制到目标文件夹。
为什么会这样?我如何解决这个问题,使所有的文件将复制到目标文件夹在正常的应用程序运行,而不是一步一步?
你的问题不是ThreadPool
。出错的是参数
在第一个代码片段中,用两个参数值填充对象数组,并将其传递给queue -方法。这里发生的是,你总是使用相同的数组。在foreach
循环的第一次迭代中,你把两个值写入数组,传递给它。最终,该方法在ThreadPool
中执行,使用该对象数组。与此同时,在foreach
-循环的第二次迭代中,您再次写入该数组并将其再次传递给ThreadPool
。这意味着两个(或更多)线程开始处理该数组。
您不知道copyfiles -方法何时处于活动状态,因此您无法判断何时将数组打开并准备好重新使用。这可以通过互斥来实现(c#中最简单的方法是使用lock
-关键字),但这不是你应该在这里使用的方式。
更好的方法是在foreach
循环的每次迭代中创建新的对象数组。只需将代码更改为:
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);
}