Task.WhenAll
在.NET Core 3.0上运行时,我只是对该方法感到好奇。我将一个简单Task.Delay
任务作为单个参数传递给Task.WhenAll
,并且希望包装后的任务的行为与原始任务相同。但这种情况并非如此。原始任务的延续是异步执行的(这是理想的),多个Task.WhenAll(task)
包装的延续是一个接一个地同步执行的(这是不希望的)。
这是此行为的演示。四个辅助任务正在等待同一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);
这是输出。四个延续在不同线程(并行)中按预期运行。
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
导致相同的奇怪行为。
另一个观察结果:我希望将包装器包装在a中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
ed 时无法完成时,才可以复制。
await Task.WhenAll(new[] { task });
什么?