2016-02-12 19 views
5

Ho un'attività che legge un file di grandi dimensioni riga per riga, ha una logica con esso e restituisce una stringa che è necessario scrivere in un file. L'ordine dell'output non ha importanza. Tuttavia, quando provo il codice qui sotto, si ferma/diventa molto lento dopo aver letto 15-20k linee del mio file.Come scrivere correttamente su un file usando Parallel.ForEach?

public static Object FileLock = new Object(); 
... 
Parallel.ForEach(System.IO.File.ReadLines(inputFile), (line, _, lineNumber) => 
{ 
    var output = MyComplexMethodReturnsAString(line); 
    lock (FileLock) 
    { 
     using (var file = System.IO.File.AppendText(outputFile)) 
     { 
      file.WriteLine(output); 
     } 
    } 
}); 

Perché il mio programma rallenta dopo un certo periodo di tempo? C'è un modo più corretto per eseguire questa attività?

+0

Avete bisogno l'ordine delle linee di uscita per corrispondere alla ordine di input? In tal caso, 'Parallel.ForEach' non è lo strumento giusto. – adv12

+0

No, l'ordine delle linee di uscita non ha importanza. – justindao

+1

Non ne sono sicuro, ma ritengo che l'utilizzo del parallelismo in questo modo stia creando/peggiorando il collo di bottiglia dell'IO invece di evitarlo. A meno che tu non stia facendo operazioni __ realmente costose su quelle linee .. – TaW

risposta

4

Hai praticamente serializzato la tua query facendo in modo che tutti i thread provino a scrivere sul file. Invece, dovresti calcolare ciò che deve essere scritto, quindi scriverli mentre arrivano alla fine.

var processedLines = File.ReadLines(inputFile).AsParallel() 
    .Select(l => MyComplexMethodReturnsAString(l)); 
File.AppendAllLines(outputFile, processedLines); 

Se è necessario per svuotare i dati come viene, aprire un flusso e attivare risciacquo automatico (o filo manualmente):

var processedLines = File.ReadLines(inputFile).AsParallel() 
    .Select(l => MyComplexMethodReturnsAString(l)); 
using (var output = File.AppendText(outputFile)) 
{ 
    output.AutoFlush = true; 
    foreach (var processedLine in processedLines) 
     output.WriteLine(processedLine); 
} 
+0

se il file è veramente un file non sono sicuro che questo approccio sia adeguato, poiché richiede la lettura dell'intero file un primo passaggio. –

+0

Non quando si utilizza 'File.ReadLines()', vi darà una enumerabile che enumererà attraverso le linee di un file mentre vengono letti. Questo è in contrasto con l'uso di 'File.ReadAllLines() 'che restituisce una matrice contenente tutte le linee di un file. _Che_ legge nell'intero file. –

1

Questo ha a che fare con il modo Parallel.ForEach s' bilanciamento del carico interno lavori. Quando vede che i tuoi thread passano molto tempo a bloccarsi, ragiona che può velocizzare le cose gettando più thread al problema, portando a maggiori costi di gestione paralleli, contesa per il tuo FileLock e al peggioramento delle prestazioni complessive.

Perché sta succedendo? Perché Parallel.ForEach non è pensato per il lavoro di I/O.

Come si può risolvere questo? Utilizzare Parallel.ForEach solo per la CPU e eseguire tutti gli IO al di fuori del loop parallelo.

Una soluzione pratica è quello di limitare il numero di fili Parallel.ForEach è permesso di arruolare base sovraccarico che accetta ParallelOptions, in questo modo:

Parallel.ForEach(
    System.IO.File.ReadLines(inputFile), 
    new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, 
    (line, _, lineNumber) => 
    { 
     ... 
    } 
+0

Mi piace molto la risposta fino alla "Soluzione rapida ...". Sembra davvero un passo indietro da tutto ciò che hai detto prima. Forse se avessi messo a punto il codice avrebbe avuto più senso per me. – Enigmativity

+0

Curioso: avevo assunto che Environment.ProcessorCount fosse il limite naturale di MaxDegreeOfParallelism comunque. È sbagliato? – TaW

+1

@TaW, no, andrà ben oltre 'Environment.ProcessorCount'. Ecco un violino che mostra circa 1 thread al secondo che viene aggiunto fino a che non si interrompe il processo (ho rinunciato dopo 100): https://dotnetfiddle.net/dT1eBM (inutile dire che probabilmente non dovresti averlo in esecuzione sulla tua produzione server) –

Problemi correlati