Parallel.ForEach中的File.Copy

我正在尝试创建一个目录并在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 

我尝试在Parallel.ForEach循环中调用CreateFolderAndCopyFile方法。

  private static void Func(IEnumerable docs) { int index = 0; Parallel.ForEach(docs, doc => { CreateFolderAndCopyFile(index); index++; }); } 

当我运行此代码时,它完成以下错误:

该进程无法访问文件’c:\ testdata \ Data00001102 \ test.pdf’,因为它正由另一个进程使用。

但首先它创建了1111个新文件夹,并在我收到此错误之前复制了test.pdf约1111次。

是什么导致了这种行为以及如何解决?

编辑:

上面的代码是玩具样本,对于硬编码字符串很抱歉结论:并行方法很慢。

明天我尝试了一些方法, 如何在C#中编写超快文件流代码? 。

特别是: http : //designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/

您没有同步访问index ,这意味着您有一个竞争对手。 这就是你有错误的原因。 为了便于说明,您可以通过使用Interlocked.Increment来避免竞争并保持此特定设计。

 private static void Func(IEnumerable docs) { int index = -1; Parallel.ForEach( docs, doc => { int nextIndex = Interlocked.Increment(index); CreateFolderAndCopyFile(nextIndex); } ); } 

但是,正如其他人所说,提供循环索引的ForEach的替代重载显然是解决这个特定问题的一个更清晰的解决方案。

但是当你使它工作时,你会发现复制文件是IO绑定而不是处理器绑定,我预测并行代码将比串行代码慢。

您对index增量操作是可疑的,因为它不是线程安全的。 如果将操作更改为Console.WriteLine("{0}", index++)您将看到此行为。

相反,您可以使用带有循环索引的Parallel.ForEach重载:

 private static void Func(IEnumerable docs) { // nb: index is 'long' not 'int' Parallel.ForEach(docs, (doc, state, index) => { CreateFolderAndCopyFile(index); }); }