2012-03-28 12 views
8

Estoy tratando de crear un directorio y copiar un archivo (pdf) dentro de un Parallel.ForEach.File.Copy en Parallel.ForEach

A continuación se muestra un ejemplo sencillo:

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); 

    } 

El método anterior crea una nueva carpeta y copia el archivo PDF en una nueva carpeta. Se crea este árbol dir:

TESTDATA 
    -Data00000000 
     -test.pdf 
    -Data00000001 
     -test.pdf 
.... 
    -Data0000000N 
     -test.pdf 

Traté de llamar al método CreateFolderAndCopyFile en un bucle Parallel.ForEach.

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

Cuando ejecuto el código termina con el siguiente error:

The process cannot access the file 'c:\testdata\Data00001102\test.pdf' because it is being used by another process.

Pero primero creado 1111 nuevas carpetas y copié test.pdf aproximadamente 1111 veces antes de que llegara este error.

¿Qué causó este comportamiento y cómo se puede resolver?

Editado:

Código anterior se muestra de juguete, lo siento por cadenas cifrados duros Conclusión: Método paralelo es lento.

Mañana pruebo algunos métodos de How to write super-fast file-streaming code in C#?.

especialmente: http://designingefficientsoftware.wordpress.com/2011/03/03/efficient-file-io-from-csharp/

+8

Como acotación al margen, que es mejor usar 'Path.Combine' en lugar de la concatenación de la ruta de sí mismo. –

+2

@Mike Noté que aún no ha votado o aceptado una respuesta en el sitio. Te sugiero que leas el [faq] para conocer estos aspectos de la comunidad de Stack Overflow. –

Respuesta

18

No está sincronizando el acceso a index y eso significa que tiene una carrera en él. Es por eso que tienes el error. Para fines ilustrativos, puede evitar la carrera y mantener este diseño particular usando Interlocked.Increment.

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

Sin embargo, como otros sugieren, la sobrecarga alternativa de ForEach que proporciona un índice de bucle es claramente una solución más limpia a este problema particular.

Pero cuando lo ponga en funcionamiento encontrará que la copia de archivos está unida a IO en lugar de unida al procesador y predigo que el código paralelo será más lento que el código de serie.

+0

Esto tiene que ser el foreach más complicado que he visto en mi vida. Felicitaciones Parallels! –

+1

@BrianGraham A 2 line ForEach es complicado? –

+1

@AndrewFinnell Supongo que Brian se está refiriendo al manejo del 'índice' –

6

Su operación de incremento en index es sospechosa ya que no es segura para subprocesos. Si cambia la operación a Console.WriteLine("{0}", index++), verá este comportamiento.

su lugar se puede utilizar una sobrecarga Parallel.ForEach con un índice del bucle:

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