我尝试学习如何使用线程池和互斥锁,作为一种练习,我尝试制作一个应用程序,将文件从计算机中的一个路径复制到计算机中的另一个路径。 为了完成这个应用程序,我使用了线程池(因此可以同时发生几个副本):
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 并且每个文件路径都被传输为参数仅一次)。
为了解决这个问题,我尝试使用 Mutex,现在 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
。问题出在争论上。
在第一个代码片段中,您用两个参数值填充对象数组并将其传递给队列方法。这里发生的情况是,您始终使用相同的数组。因此,在 foreach 循环的第一次迭代中,您将两个值写入数组并传递它。最终,该方法使用该对象数组在线程池中执行。同时,在 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);
}
关于c# - 如何在c#中使用线程池和互斥锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31696357/