c# - 为什么要继续执行Task.WhenAll的延续?

标签 c# .net-core async-await task-parallel-library continuations

在.NET Core 3.0上运行时,我只是对 Task.WhenAll 方法感到好奇。我将一个简单的Task.Delay任务作为单个参数传递给Task.WhenAll,我希望包装后的任务的行为将与原始任务相同。但这种情况并非如此。原始任务的延续是异步执行的(这是理想的),多个Task.WhenAll(task)包装器的延续是一个接一个地同步执行的(这是不希望的)。

这是此行为的demo。四个工作程序任务正在等待同一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

令人惊讶的是,仅当每个 worker 都在等待不同的包装器时,才会发生这种情况。如果我预先定义包装器:
var task = Task.WhenAll(Task.Delay(500));

...然后用await在所有worker中执行相同的任务,其行为与第一种情况(异步延续)相同。

我的问题是:为什么会这样?是什么导致同一任务的不同包装器的延续在同一线程中同步执行?

注意: Task.WhenAny 而不是Task.WhenAll包装任务会导致相同的奇怪行为。

另一个观察结果:我希望将包装器包装在Task.Run中会使连续体异步进行。但这没有发生。下面的行的继续仍然在同一线程中执行(同步)。
await Task.Run(async () => await Task.WhenAll(task));

澄清:在.NET Core 3.0平台上运行的控制台应用程序中观察到了以上差异。在.NET Framework 4.8上,等待原始任务或任务包装器没有区别。在这两种情况下,连续都在同一线程中同步执行。

最佳答案

因此,您有多个异步方法在等待相同的任务变量;

    await task;
    // CPU heavy operation

是的,这些继续将在task完成时被依次调用。在您的示例中,每个延续然后将线程占用下一秒。

如果您希望每个延续都异步运行,则可能需要类似以下内容:

    await task;
    await Task.Yield().ConfigureAwait(false);
    // CPU heavy operation

这样您的任务将从初始延续中返回,并允许CPU负载在SynchronizationContext之外运行。

关于c# - 为什么要继续执行Task.WhenAll的延续?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60611321/

相关文章:

c# - MachineKey.Protect 和 MachineKey.Unprotect

c# - .net core 2.0 在 ubuntu 上运行控制台应用程序时出错

asp.net-core - 如何避免 Web API 中的 JSON 反序列化/序列化?

c# - MySQL.Data 到 Pomelo - MysqlReader 更改

c# - ValueTask 有 ContinueWith 吗?

c# - 新的await关键字在高度并行环境中有用吗?

javascript - js等待 promise 调用自身

c# - Linq-To-Entities 使用方法选择新对象

c# - HttpClient 中有多少个连接

c# - 使用 XmlSerializer 在不包含节点的情况下序列化名称值对数组