我正在尝试创建一个目录并在Parallel.ForEach
内复制一个文件(pdf)。
下面是一个简单的例子:
private static void CreateFolderAndCopyFile(int index)
{
const string sourcePdfPath = "c:\\testdata\\test.pdf";
const string rootPath = "c:\\testdata";
string folderDirName = string.Format("Data{0}", string.Format("{0:00000000}", index));
string folderDirPath = rootPath + @"\" + folderDirName;
Directory.CreateDirectory(folderDirPath);
string desPdfPath = folderDirPath + @"\" + "test.pdf";
File.Copy(sourcePdfPath, desPdfPath, true);
}
上述方法创建一个新文件夹并将pdf文件复制到新文件夹。 它创建了这个目录树:
TESTDATA
-Data00000000
-test.pdf
-Data00000001
-test.pdf
....
-Data0000000N
-test.pdf
我尝试在CreateFolderAndCopyFile
循环中调用Parallel.ForEach
方法。
private static void Func<T>(IEnumerable<T> docs)
{
int index = 0;
Parallel.ForEach(docs, doc =>
{
CreateFolderAndCopyFile(index);
index++;
});
}
当我运行此代码时,它完成以下错误:
该进程无法访问文件'c:\ testdata \ Data00001102 \ test.pdf' 因为它正被另一个进程使用。
但首先它创建了1111个新文件夹,并在我收到此错误之前将test.pdf复制了大约1111次。
导致此行为的原因以及如何解决?
已编辑:
上面的代码是玩具样本,对于硬编码字符串很抱歉 结论:并行方法很慢。
明天我会尝试How to write super-fast file-streaming code in C#?的一些方法。
特别是:http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/
答案 0 :(得分:18)
您没有同步index
的访问权限,这意味着您正在竞争。这就是你有错误的原因。为了便于说明,您可以使用Interlocked.Increment
来避免竞争并保持此特定设计。
private static void Func<T>(IEnumerable<T> docs)
{
int index = -1;
Parallel.ForEach(
docs, doc =>
{
int nextIndex = Interlocked.Increment(index);
CreateFolderAndCopyFile(nextIndex);
}
);
}
然而,正如其他人所说,提供循环索引的ForEach
的替代重载显然是解决这一特定问题的更清晰的解决方案。
但是当你开始工作时,你会发现复制文件是IO绑定而不是处理器绑定,我预测并行代码将比串行代码慢。
答案 1 :(得分:7)
index
上的增量操作是可疑的,因为它不是线程安全的。如果将操作更改为Console.WriteLine("{0}", index++)
,您将看到此行为。
相反,您可以使用带有循环索引的Parallel.ForEach
重载:
private static void Func<T>(IEnumerable<T> docs)
{
// nb: index is 'long' not 'int'
Parallel.ForEach(docs, (doc, state, index) =>
{
CreateFolderAndCopyFile(index);
});
}