Task.WhenAll
.NET Core 3.0で実行しているときに、私はメソッドについて奇妙な観察をしました。単純なTask.Delay
タスクを単一の引数としてに渡しましたTask.WhenAll
が、ラップされたタスクは元のタスクと同じように動作するはずです。しかし、そうではありません。元のタスクの継続は非同期で実行され(これは望ましい)、複数のTask.WhenAll(task)
ラッパーの継続は1つずつ同期して実行されます(これは望ましくありません)。
ここでデモこの動作のは。4つのワーカータスクが同じTask.Delay
タスクが完了するのを待ってから、重い計算(でシミュレート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);
これが出力です。4つの継続は、異なるスレッドで(並列に)期待どおりに実行されています。
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
ここで、この行await task
をコメント化し、次の行のコメントを外すawait Task.WhenAll(task)
と、出力はかなり異なります。すべての継続は同じスレッドで実行されるため、計算は並列化されません。各計算は、前の計算の完了後に開始されます。
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
驚くべきことに、これは各ワーカーが異なるラッパーを待機している場合にのみ発生します。ラッパーを事前に定義すると、次のようになります。
var task = Task.WhenAll(Task.Delay(500));
...そしてawait
すべてのワーカー内で同じタスクを実行すると、動作は最初のケースと同じになります(非同期継続)。
私の質問は、なぜこれが起こっているのですか?同じタスクの異なるラッパーの継続が同じスレッドで同期的に実行される原因は何ですか?
注:Task.WhenAny
ではなくでタスクをラップするとTask.WhenAll
、同じ奇妙な動作になります。
別の観察:ラッパーをの中にラップTask.Run
すると、継続が非同期になると予想していました。しかし、それは起こっていません。以下の行の続きは、引き続き同じスレッドで(同期的に)実行されます。
await Task.Run(async () => await Task.WhenAll(task));
明確化:上記の違いは、.NET Core 3.0プラットフォームで実行されるコンソールアプリケーションで観察されました。.NET Framework 4.8では、元のタスクの待機とタスクラッパーの待機の間に違いはありません。どちらの場合も、継続は同じスレッドで同期的に実行されます。
Task.WhenAll
Task.Delay
から100
に変更した後にのみです。1000
await
await Task.WhenAll(new[] { task });
ますか?