Parallel.ForEach vs Task.Run und Task.WhenAll


158

Was sind die Unterschiede zwischen der Verwendung von Parallel.ForEach oder Task.Run (), um eine Reihe von Aufgaben asynchron zu starten?

Version 1:

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

Version 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
Ich denke, das 2. Codefragment wäre fast gleich dem 1., wenn Sie Task.WaitAllstattdessen verwenden würden Task.WhenAll.
Avo

15
Bitte beachten Sie auch, dass der zweite DoSomething ("s3") dreimal ausführt und nicht das gleiche Ergebnis liefert! stackoverflow.com/questions/4684320/…
Nullius


@Dan: Beachten Sie, dass Version 2 async / await verwendet, was bedeutet, dass es sich um eine andere Frage handelt. Async / await wurde mit VS 2012 eingeführt, 1,5 Jahre nachdem der mögliche doppelte Thread geschrieben wurde.
Petter T

Antworten:


159

In diesem Fall wartet die zweite Methode asynchron auf den Abschluss der Aufgaben, anstatt sie zu blockieren.

Jedoch gibt es einen Nachteil verwenden Task.Runin einer schlaufen mit Parallel.ForEach, gibt es eine , Partitionerdie zu vermeiden , erstellt wird , mehr Aufgaben als nötig zu machen. Task.RunEs wird immer eine einzelne Aufgabe pro Element erstellt (da Sie dies tun), aber die ParallelKlassenstapel funktionieren, sodass Sie weniger Aufgaben als die gesamten Arbeitselemente erstellen. Dies kann zu einer erheblich besseren Gesamtleistung führen, insbesondere wenn der Schleifenkörper einen geringen Arbeitsaufwand pro Element aufweist.

In diesem Fall können Sie beide Optionen kombinieren, indem Sie Folgendes schreiben:

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

Beachten Sie, dass dies auch in dieser kürzeren Form geschrieben werden kann:

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

1
Tolle Antwort, ich habe mich gefragt, ob Sie mich auf ein gutes Lesematerial zu diesem Thema verweisen können.
Dimitar Dimitrov

@ DimitarDimitrov Für allgemeine TPL-Sachen, reedcopsey.com/series/parallelism-in-net4
Reed Copsey

1
Mein Parallel.ForEach-Konstrukt hat meine Anwendung zum Absturz gebracht. Ich habe eine schwere Bildverarbeitung durchgeführt. Als ich jedoch Task.Run (() => Parallel.ForEach (....)) hinzufügte; Es hörte auf zu krachen. Kannst du erklären warum? Bitte beachten Sie, dass ich die parallelen Optionen auf die Anzahl der Kerne im System beschränke.
Monkeyjumps

3
Was passiert , wenn DoSomethingist async void DoSomething?
Francesco Bonizzi

1
Was ist mit async Task DoSomething?
Shawn Mclean

37

Die erste Version blockiert synchron den aufrufenden Thread (und führt einige der Aufgaben darauf aus).
Wenn es sich um einen UI-Thread handelt, friert dies die UI ein.

Die zweite Version führt die Aufgaben asynchron im Thread-Pool aus und gibt den aufrufenden Thread frei, bis sie fertig sind.

Es gibt auch Unterschiede bei den verwendeten Planungsalgorithmen.

Beachten Sie, dass Ihr zweites Beispiel auf gekürzt werden kann

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

2
sollte es nicht sein await Task.WhenAll(strings.Select(async s => await Task.Run(() => DoSomething(s)));? Ich hatte Probleme beim Zurückgeben von Aufgaben (anstatt zu warten), insbesondere wenn Anweisungen wie usingzum Entsorgen von Objekten beteiligt waren.
Martín Coll

Mein Parallel.ForEach-Aufruf führte zum Absturz meiner Benutzeroberfläche. Ich fügte Task.Run (() => Parallel.ForEach (....) hinzu. dazu und es löste einen Absturz auf.
Monkeyjumps

0

Am Ende tat ich dies, da es sich leichter zu lesen anfühlte:

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

Auf diese Weise werden die Aufgaben nacheinander ausgeführt oder die WhenAll starten alle auf einmal?
Vinicius Gualberto

Soweit ich das beurteilen kann, werden sie alle gestartet, wenn ich "DoSomethingAsync ()" aufrufe. Nichts blockiert sie jedoch, bis WhenAll aufgerufen wird.
Chris M.

Du meinst, wenn das erste "DoSomethingAsync ()" aufgerufen wird?
Vinicius Gualberto

1
@ ChrisM. Es wird bis zum ersten Warten auf DoSomethingAsync () blockiert, da dies die Ausführung zurück in Ihre Schleife überträgt. Wenn es synchron ist und Sie eine Aufgabe zurückgeben, wird der gesamte Code nacheinander ausgeführt und das WhenAll wartet, bis alle Aufgaben abgeschlossen sind
Simon Belanger,

0

Ich habe gesehen, dass Parallel.ForEach unangemessen verwendet wurde, und ich dachte, ein Beispiel in dieser Frage würde helfen.

Wenn Sie den folgenden Code in einer Konsolen-App ausführen, sehen Sie, wie die in Parallel.ForEach ausgeführten Aufgaben den aufrufenden Thread nicht blockieren. Dies kann in Ordnung sein, wenn Sie sich nicht für das Ergebnis interessieren (positiv oder negativ), aber wenn Sie das Ergebnis benötigen, sollten Sie sicherstellen, dass Sie Task.WhenAll verwenden.

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}]");
        }
    }
}

Hier ist das Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Fazit:

Wenn Sie Parallel.ForEach mit einer Aufgabe verwenden, wird der aufrufende Thread nicht blockiert. Wenn Sie sich für das Ergebnis interessieren, warten Sie auf die Aufgaben.

~ Prost

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.