为什么要连续执行Task.WhenAll?

问题描述 投票:0回答:1

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

这里是此行为的演示。四个辅助任务正在等待同一Task.WhenAll(task)任务完成,然后继续进行大量的计算(由Task.Delay模拟)。

Thread.Sleep

这里是输出。这四个延续在不同的线程(并行)中按预期运行。

var task = Task.Delay(100);
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(100)); 所有工作人员中的同一任务,其行为与第一种情况(异步延续)相同。

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

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

另一个观察结果:我希望将包装器包装在Task.WhenAny内将使延续成为异步。但这没有发生。下面的行的继续仍然在同一线程中执行(同步)。

Task.WhenAll

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

c# .net-core async-await task-parallel-library continuations
1个回答
1
投票

所以您有多个异步方法在等待相同的任务变量;

Task.Run

是,当await Task.Run(async () => await Task.WhenAll(task)); 完成时,将连续调用这些继续。在您的示例中,每个延续然后将线程占用下一秒。

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

    await task;
    // CPU heavy operation

以便您的任务从初始继续处返回,并允许CPU负载在task之外运行。

© www.soinside.com 2019 - 2024. All rights reserved.