The*_*ias 18 c# continuations task-parallel-library async-await .net-core
Task.WhenAll
在 .NET Core 3.0 上运行时,我只是对该方法进行了一个奇怪的观察。我将一个简单的Task.Delay
任务作为单个参数传递给Task.WhenAll
,并且我预计包装的任务将与原始任务的行为相同。但这种情况并非如此。原始任务的延续是异步执行的(这是可取的),多个Task.WhenAll(task)
包装器的延续是一个接一个同步执行的(这是不可取的)。
这是此行为的演示。四个工作任务正在等待同一个Task.Delay
任务完成,然后继续进行繁重的计算(由 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);
Run Code Online (Sandbox Code Playgroud)
这是输出。四个延续在不同的线程(并行)中按预期运行。
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);
Run Code Online (Sandbox Code Playgroud)
现在,如果我注释该行await task
并取消注释以下行await Task.WhenAll(task)
,则输出完全不同。所有延续都在同一个线程中运行,因此计算不是并行化的。每个计算都在前一个计算完成后开始:
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
Run Code Online (Sandbox Code Playgroud)
令人惊讶的是,只有当每个工人等待不同的包装器时才会发生这种情况。如果我预先定义包装器:
var task = Task.WhenAll(Task.Delay(500));
Run Code Online (Sandbox Code Playgroud)
...然后await
在所有工人中执行相同的任务,行为与第一种情况(异步延续)相同。
我的问题是:为什么会这样?是什么导致同一任务的不同包装器的延续在同一线程中同步执行?
注意:使用Task.WhenAny
而不是包装任务会Task.WhenAll
导致相同的奇怪行为。
另一个观察结果:我预计将包装器包装在 a 中Task.Run
会使延续异步。但它没有发生。下面这行的延续仍然在同一个线程中(同步)执行。
await Task.Run(async () => await Task.WhenAll(task));
Run Code Online (Sandbox Code Playgroud)
说明:上述差异是在 .NET Core 3.0 平台上运行的控制台应用程序中观察到的。在 .NET Framework 4.8 上,等待原始任务或任务包装器之间没有区别。在这两种情况下,continuation 都是在同一个线程中同步执行的。
因此,您有多个异步方法等待同一个任务变量;
await task;
// CPU heavy operation
Run Code Online (Sandbox Code Playgroud)
是的,这些延续在完成后将被系列调用task
。在您的示例中,每个延续都会在下一秒占用线程。
如果您希望每个延续异步运行,您可能需要类似的东西;
await task;
await Task.Yield().ConfigureAwait(false);
// CPU heavy operation
Run Code Online (Sandbox Code Playgroud)
以便您的任务从初始延续返回,并允许 CPU 负载在SynchronizationContext
.
归档时间: |
|
查看次数: |
957 次 |
最近记录: |