Ich habe gerade eine merkwürdige Beobachtung bezüglich der Task.WhenAll
Methode gemacht, als ich unter .NET Core 3.0 lief. Ich habe eine einfache Task.Delay
Aufgabe als einzelnes Argument übergeben Task.WhenAll
und erwartet, dass sich die umschlossene Aufgabe identisch mit der ursprünglichen Aufgabe verhält. Dies ist jedoch nicht der Fall. Die Fortsetzungen der ursprünglichen Aufgabe werden asynchron ausgeführt (was wünschenswert ist), und die Fortsetzungen mehrerer Task.WhenAll(task)
Wrapper werden nacheinander synchron ausgeführt (was unerwünscht ist).
Hier ist eine Demo dieses Verhaltens. Vier Worker-Aufgaben warten darauf, dass dieselbe Task.Delay
Aufgabe erledigt wird, und fahren dann mit einer umfangreichen Berechnung fort (simuliert durch a Thread.Sleep
).
var task = Task.Delay(500);
var workers = Enumerable.Range(1, 4).Select(async x =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
$" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} before await");
await task;
//await Task.WhenAll(task);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff}" +
$" [{Thread.CurrentThread.ManagedThreadId}] Worker{x} after await");
Thread.Sleep(1000); // Simulate some heavy CPU-bound computation
}).ToArray();
Task.WaitAll(workers);
Hier ist die Ausgabe. Die vier Fortsetzungen werden erwartungsgemäß in verschiedenen Threads (parallel) ausgeführt.
05:23:25.511 [1] Worker1 before await
05:23:25.542 [1] Worker2 before await
05:23:25.543 [1] Worker3 before await
05:23:25.543 [1] Worker4 before await
05:23:25.610 [4] Worker1 after await
05:23:25.610 [7] Worker2 after await
05:23:25.610 [6] Worker3 after await
05:23:25.610 [5] Worker4 after await
Wenn ich nun die Zeile kommentiere await task
und die folgende Zeile auskommentiere await Task.WhenAll(task)
, ist die Ausgabe ganz anders. Alle Fortsetzungen werden im selben Thread ausgeführt, sodass die Berechnungen nicht parallelisiert werden. Jede Berechnung beginnt nach Abschluss der vorherigen:
05:23:46.550 [1] Worker1 before await
05:23:46.575 [1] Worker2 before await
05:23:46.576 [1] Worker3 before await
05:23:46.576 [1] Worker4 before await
05:23:46.645 [4] Worker1 after await
05:23:47.648 [4] Worker2 after await
05:23:48.650 [4] Worker3 after await
05:23:49.651 [4] Worker4 after await
Überraschenderweise geschieht dies nur, wenn jeder Mitarbeiter auf einen anderen Wrapper wartet. Wenn ich den Wrapper im Voraus definiere:
var task = Task.WhenAll(Task.Delay(500));
... und dann await
die gleiche Aufgabe in allen Arbeitern, das Verhalten ist identisch mit dem ersten Fall (asynchrone Fortsetzungen).
Meine Frage ist: Warum passiert das? Was bewirkt, dass die Fortsetzungen verschiedener Wrapper derselben Aufgabe synchron im selben Thread ausgeführt werden?
Hinweis: Das Umschließen einer Aufgabe mit Task.WhenAny
statt Task.WhenAll
führt zu demselben seltsamen Verhalten.
Eine weitere Beobachtung: Ich hatte erwartet, dass das Umschließen des Wrappers in a Task.Run
die Fortsetzungen asynchron machen würde. Aber es passiert nicht. Die Fortsetzungen der folgenden Zeile werden weiterhin im selben Thread ausgeführt (synchron).
await Task.Run(async () => await Task.WhenAll(task));
Erläuterung: Die oben genannten Unterschiede wurden in einer Konsolenanwendung beobachtet, die auf der .NET Core 3.0-Plattform ausgeführt wird. In .NET Framework 4.8 gibt es keinen Unterschied zwischen dem Warten auf die ursprüngliche Aufgabe oder dem Task-Wrapper. In beiden Fällen werden die Fortsetzungen synchron im selben Thread ausgeführt.
Task.WhenAll
Task.Delay
von 100
auf 1000
geändert hatte, damit sie beim await
Bearbeiten nicht abgeschlossen wird .
await Task.WhenAll(new[] { task });
?