Parallel.ForEach中的File.Copy

时间:2012-03-28 18:07:04

标签: c# parallel-processing file-copying

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

2 个答案:

答案 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);
                            });
}