Parallel.ForEach vs Task.Run e Task.WhenAll


158

Quali sono le differenze tra l'utilizzo di Parallel.ForEach o Task.Run () per avviare una serie di attività in modo asincrono?

Versione 1:

List<string> strings = new List<string> { "s1", "s2", "s3" };
Parallel.ForEach(strings, s =>
{
    DoSomething(s);
});

Versione 2:

List<string> strings = new List<string> { "s1", "s2", "s3" };
List<Task> Tasks = new List<Task>();
foreach (var s in strings)
{
    Tasks.Add(Task.Run(() => DoSomething(s)));
}
await Task.WhenAll(Tasks);

3
Penso che il secondo frammento di codice sarebbe quasi uguale al primo se si utilizzasse Task.WaitAllinvece di Task.WhenAll.
evitare il

15
Nota anche che il secondo eseguirà DoSomething ("s3") tre volte e non produrrà lo stesso risultato! stackoverflow.com/questions/4684320/…
Nullius


@Dan: nota che la versione 2 usa async / await, il che significa che è una domanda diversa. Async / waitit è stato introdotto con VS 2012, 1,5 anni dopo la scrittura del possibile thread duplicato.
Petter T

Risposte:


159

In questo caso, il secondo metodo attenderà in modo asincrono il completamento delle attività anziché il blocco.

Tuttavia, c'è uno svantaggio da usare Task.Runin un ciclo: con Parallel.ForEach, c'è uno Partitionerche viene creato per evitare di fare più attività del necessario. Task.Runeseguirà sempre un'unica attività per articolo (dal momento che lo stai facendo), ma i Parallelbatch di classe funzionano in modo da creare meno attività rispetto al totale degli elementi di lavoro. Ciò può fornire prestazioni complessive significativamente migliori, specialmente se il corpo del loop ha una piccola quantità di lavoro per articolo.

In questo caso, puoi combinare entrambe le opzioni scrivendo:

await Task.Run(() => Parallel.ForEach(strings, s =>
{
    DoSomething(s);
}));

Si noti che questo può anche essere scritto in questa forma più breve:

await Task.Run(() => Parallel.ForEach(strings, DoSomething));

1
Ottima risposta, mi chiedevo se potessi indicarmi un buon materiale di lettura su questo argomento?
Dimitar Dimitrov,

@DimitarDimitrov Per informazioni generali su TPL, reedcopsey.com/series/parallelism-in-net4
Reed Copsey

1
Il mio costrutto Parallel.ForEach stava bloccando la mia applicazione. Stavo eseguendo una pesante elaborazione delle immagini al suo interno. Tuttavia, quando ho aggiunto Task.Run (() => Parallel.ForEach (....)); Ha smesso di schiantarsi. Puoi spiegare perché? Notare che vincolo le opzioni parallele al numero di core sul sistema.
monkeyjumps,

3
E se DoSomethingfosse async void DoSomething?
Francesco Bonizzi,

1
Che dire async Task DoSomething?
Shawn Mclean,

37

La prima versione bloccherà in modo sincrono il thread chiamante (ed eseguirà alcune delle attività su di esso).
Se si tratta di un thread dell'interfaccia utente, ciò bloccherà l'interfaccia utente.

La seconda versione eseguirà le attività in modo asincrono nel pool di thread e rilascerà il thread chiamante fino al termine.

Esistono anche differenze negli algoritmi di pianificazione utilizzati.

Nota che il tuo secondo esempio può essere abbreviato in

await Task.WhenAll(strings.Select(s => Task.Run(() => DoSomething(s)));

2
non dovrebbe essere await Task.WhenAll(strings.Select(async s => await Task.Run(() => DoSomething(s)));? Ho avuto problemi quando ho restituito compiti (invece di aspettare), specialmente quando usingsono state coinvolte affermazioni simili per disporre oggetti.
Martín Coll,

La mia chiamata Parallel.ForEach causava l'arresto anomalo dell'interfaccia utente. Ho aggiunto Task.Run (() => Parallel.ForEach (....)); e ha risolto il crash.
monkeyjumps,

1

Ho finito per farlo, perché mi è sembrato più facile leggere:

  List<Task> x = new List<Task>();
  foreach(var s in myCollectionOfObject)
  {
      // Note there is no await here. Just collection the Tasks
      x.Add(s.DoSomethingAsync());
  }
  await Task.WhenAll(x);

In questo modo le attività vengono eseguite una dopo l'altra oppure quando AllAll le sta avviando tutte in una volta?
Vinicius Gualberto,

Per quanto ne so, sono tutti avviati quando chiamo "DoSomethingAsync ()". Nulla li blocca, tuttavia, fino a quando viene chiamato WhenAll.
Chris M.

Intendi quando viene chiamato il primo "DoSomethingAsync ()"?
Vinicius Gualberto,

1
@Crisma. Sarà bloccato fino alla prima attesa di DoSomethingAsync () poiché questo è ciò che trasferirà l'esecuzione al tuo loop. Se è sincrono e restituisci un'attività, tutto il codice verrà eseguito uno dopo l'altro e il WhenAll attenderà il completamento di tutte le attività
Simon Belanger

0

Ho visto Parallel.For Ognuno usato in modo inappropriato, e ho pensato che un esempio in questa domanda sarebbe stato d'aiuto.

Quando esegui il codice qui sotto in un'app Console, vedrai come le attività eseguite in Parallel.ForEach non blocca il thread chiamante. Questo potrebbe andare bene se non ti interessa il risultato (positivo o negativo) ma se hai bisogno del risultato, dovresti assicurarti di usare Task.WhenAll.

using System;
using System.Linq;
using System.Threading.Tasks;

namespace ParrellelEachExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var indexes = new int[] { 1, 2, 3 };

            RunExample((prefix) => Parallel.ForEach(indexes, (i) => DoSomethingAsync(i, prefix)),
                "Parallel.Foreach");

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("*You'll notice the tasks haven't run yet, because the main thread was not blocked*");
            Console.WriteLine("Press any key to start the next example...");
            Console.ReadKey();

            RunExample((prefix) => Task.WhenAll(indexes.Select(i => DoSomethingAsync(i, prefix)).ToArray()).Wait(),
                "Task.WhenAll");
            Console.WriteLine("All tasks are done.  Press any key to close...");
            Console.ReadKey();
        }

        static void RunExample(Action<string> action, string prefix)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine($"{Environment.NewLine}Starting '{prefix}'...");
            action(prefix);
            Console.WriteLine($"{Environment.NewLine}Finished '{prefix}'{Environment.NewLine}");
        }


        static async Task DoSomethingAsync(int i, string prefix)
        {
            await Task.Delay(i * 1000);
            Console.WriteLine($"Finished: {prefix}[{i}]");
        }
    }
}

Ecco il risultato:

inserisci qui la descrizione dell'immagine

Conclusione:

L'uso di Parallel.ForEach con un'attività non bloccherà il thread chiamante. Se ti interessa il risultato, assicurati di attendere i compiti.

~ Saluti

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.