【问题标题】:File.Copy in Parallel.ForEachFile.Copy in Parallel.ForEach
【发布时间】:2012-04-12 09:13:08
【问题描述】:

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

【问题讨论】:

  • 顺便说一句,您可能应该使用Path.Combine 而不是自己连接路径。
  • @Mike 我注意到您尚未在网站上投票或接受答案。我建议您阅读 faq 以了解 Stack Overflow 社区的这些方面。

标签: c# parallel-processing file-copying


【解决方案1】:

您没有同步对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 绑定而不是处理器绑定,我预测并行代码会比串行代码慢。

【讨论】:

  • 这一定是我见过的最复杂的 foreach。恭喜 Parallels!
  • @BrianGraham A 2 line ForEach 很复杂?
  • @AndrewFinnell 我想布赖恩指的是index 处理
  • @AndrewFinnell for (int i = 0; i &lt; docs.Count; i++) CreateFolderAndCopyFile(i);
  • 您不会比系统提供的文件复制例程做得更好。
【解决方案2】:

您对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);
                            });
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-13
    • 2016-12-14
    • 2011-10-23
    • 1970-01-01
    • 2013-09-04
    • 1970-01-01
    • 2012-11-29
    • 2018-04-22
    相关资源
    最近更新 更多