2012-03-28 11 views
8

Sto cercando di creare una directory e copiare un file (pdf) all'interno di uno Parallel.ForEach.File.Copy in Parallel.ForEach

Di seguito è riportato un semplice esempio:

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

    } 

Il metodo di cui sopra crea una nuova cartella e copia il file pdf in una nuova cartella. Si crea questo albero dir:

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

Ho provato a chiamare il metodo CreateFolderAndCopyFile in un ciclo Parallel.ForEach.

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

Quando ho eseguito questo codice termina con il seguente errore:

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

Ma prima ha creato 1111 nuove cartelle e copiato test.pdf circa 1111 volte prima ho ottenuto questo errore.

Che cosa ha causato questo comportamento e come può essere risolto?

Modificato:

codice sopra è stato campione giocattolo, mi spiace per le stringhe hardcoded Conclusione: metodo parallelo è lento.

Domani provo alcuni metodi da How to write super-fast file-streaming code in C#?.

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

+8

Per inciso, probabilmente si dovrebbe usare 'Path.Combine' invece di concatenare il percorso da soli. –

+2

@Mike noto che non hai ancora votato o accettato una risposta sul sito. Posso suggerire di leggere il [faq] per conoscere questi aspetti della community di Stack Overflow. –

risposta

18

Non sta eseguendo la sincronizzazione l'accesso a index e che significa avere una corsa su di esso. Ecco perché hai l'errore. A scopo illustrativo, puoi evitare la gara e mantenere questo particolare design 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); 
     } 
    ); 
} 

Tuttavia, come altri suggeriscono, il sovraccarico alternativa di ForEach che fornisce un indice del ciclo è chiaramente una soluzione detergente a questo particolare problema.

Ma quando lo si fa funzionare si scoprirà che copiare i file è legato all'IO piuttosto che al processore e prevedo che il codice parallelo sarà più lento del codice seriale.

+0

Questo deve essere il foreach più complicato che abbia mai visto. Congratulazioni Parallels! –

+1

@BrianGraham Una linea a due linee ForEach è complicata? –

+1

@AndrewFinnell Suppongo che Brian si riferisca alla gestione 'index' –

6

L'operazione di incremento su index è sospetta in quanto non è thread-safe. Se si modifica l'operazione in Console.WriteLine("{0}", index++), verrà visualizzato questo comportamento.

Invece si potrebbe utilizzare un overload Parallel.ForEach con un indice del ciclo:

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