File.Copy в Parallel.ForEach - PullRequest
       9

File.Copy в Parallel.ForEach

8 голосов
/ 28 марта 2012

Я пытаюсь создать каталог и скопировать файл (pdf) внутри Parallel.ForEach.

Ниже приведен простой пример:

    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 раз, прежде чем я получил эту ошибку.

Что вызвало такое поведение и как его можно устранить?

РЕДАКТИРОВАНИЕ:

Код выше был образцом игрушки, извините за жестко закодированные строки Вывод: параллельный метод медленный.

Завтра я попробую несколько методов из Как написать сверхбыстрый код потоковой передачи файлов на C #? .

особенно: http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/

Ответы [ 2 ]

18 голосов
/ 28 марта 2012

Вы не синхронизируете доступ к 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, а не с процессором, и я предсказываю, что параллельный код будет медленнеесерийный код.

7 голосов
/ 28 марта 2012

Ваша операция приращения на 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);
                            });
}
...