Parallel.ForEach vs Task.Factory.StartNew

Qual è la differenza tra i frammenti di codice seguenti? Non saranno entrambi utilizzati thread threadpool?

Per esempio se voglio chiamare una funzione per ogni object in una collezione,

Parallel.ForEach(items, item => DoSomething(item)); vs foreach(var item in items) { Task.Factory.StartNew(() => DoSomething(item)); } 

Il primo è un’opzione molto migliore.

Parallel.ForEach, internamente, utilizza un Partitioner per distribuire la raccolta in oggetti di lavoro. Non eseguirà un compito per articolo, ma lo farà in batch per ridurre l’overhead coinvolto.

La seconda opzione pianifica una singola Task per articolo nella tua raccolta. Mentre i risultati saranno (quasi) uguali, questo introdurrà un sovraccarico molto più del necessario, soprattutto per le grandi raccolte, e farà sì che i tempi di esecuzione complessivi siano più lenti.

Cordiali saluti – Il partizionatore utilizzato può essere controllato utilizzando gli overload appropriati per Parallel.ForEach , se lo si desidera. Per i dettagli, vedere Partizionatori personalizzati su MSDN.

La differenza principale, in fase di esecuzione, è la seconda azione asincrona. Questo può essere duplicato usando Parallel.ForEach facendo:

 Task.Factory.StartNew( () => Parallel.ForEach(items, item => DoSomething(item))); 

In questo modo, si avvantaggiano ancora i partizionatori, ma non si bloccano fino al completamento dell’operazione.

Ho fatto un piccolo esperimento di esecuzione di un metodo “1000000000” volte con “Parallel.For” e uno con oggetti “Task”.

Ho misurato il tempo del processore e ho trovato Parallel più efficiente. Parallelamente. Per dividere il tuo compito in piccoli oggetti di lavoro ed eseguirli su tutti i core parallelamente in modo ottimale. Durante la creazione di molti oggetti task (in FYI TPL verrà utilizzato internamente il pool di thread), ogni esecuzione verrà spostata su ogni attività, creando più stress nella casella, evidente dall’esperimento riportato di seguito.

Ho anche creato un piccolo video che spiega il TPL di base e ha anche dimostrato come Parallel.For utilizza il tuo core in modo più efficiente http://www.youtube.com/watch?v=No7QqSc5cl8 rispetto alle normali attività e thread.

Esperimento 1

 Parallel.For(0, 1000000000, x => Method1()); 

Esperimento 2

 for (int i = 0; i < 1000000000; i++) { Task o = new Task(Method1); o.Start(); } 

Confronto dei tempi del processore

Parallel.ForEach ottimizzerà (potrebbe anche non iniziare nuovi thread) e bloccherà fino al termine del ciclo, e Task.Factory creerà esplicitamente una nuova istanza di attività per ciascun elemento e tornerà prima che siano terminati (attività asincrone). Parallel.Foreach è molto più efficiente.

A mio avviso, lo scenario più realistico è quando le attività richiedono un intervento pesante. L’approccio di Shivprasad si concentra più sulla creazione di oggetti / allocazione di memoria che sul computing stesso. Ho fatto una ricerca chiamando il seguente metodo:

 public static double SumRootN(int root) { double result = 0; for (int i = 1; i < 10000000; i++) { result += Math.Exp(Math.Log(i) / root); } return result; } 

L'esecuzione di questo metodo richiede circa 0,5 secondi.

L'ho chiamato 200 volte usando Parallel:

 Parallel.For(0, 200, (int i) => { SumRootN(10); }); 

Poi l'ho chiamato 200 volte usando la vecchia maniera:

 List tasks = new List() ; for (int i = 0; i < loopCounter; i++) { Task t = new Task(() => SumRootN(10)); t.Start(); tasks.Add(t); } Task.WaitAll(tasks.ToArray()); 

Primo caso completato in 26656 ms, il secondo in 24478 ms. L'ho ripetuto molte volte. Ogni volta che il secondo approccio è marginalmente più veloce.