为什么简单的 await Task.Delay(1) 支持并行执行?

use*_*750 1 c# asynchronous task async-await

我想问一下关于下面代码的简单问题:

static void Main(string[] args)
{
    MainAsync()
        //.Wait();
        .GetAwaiter().GetResult();
}

static async Task MainAsync()
{
    Console.WriteLine("Hello World!");

    Task<int> a = Calc(18000);
    Task<int> b = Calc(18000);
    Task<int> c = Calc(18000);

    await a;
    await b;
    await c;

    Console.WriteLine(a.Result);
}

static async Task<int> Calc(int a)
{
    //await Task.Delay(1);
    Console.WriteLine("Calc started");

    int result = 0;

    for (int k = 0; k < a; ++k)
    {
        for (int l = 0; l < a; ++l)
        {
            result += l;
        }
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)

此示例Calc以同步方式运行函数。当该行//await Task.Delay(1);被取消注释时,Calc函数将以并行方式执行。

问题是:为什么通过添加简单的等待,该Calc函数是异步的?我知道异步/等待对要求。我问的是在函数的开头添加简单的 await Delay 时真正发生了什么。Calc然后整个函数被识别为在另一个线程中运行,但为什么呢?

编辑1:

当我向代码添加线程检查时:

static async Task<int> Calc(int a)
{
    await Task.Delay(1);
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

    int result = 0;

    for (int k = 0; k < a; ++k)
    {
        for (int l = 0; l < a; ++l)
        {
            result += l;
        }
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)

可以看到(在控制台中)不同的线程 ID。如果await Delay行被删除,则 Calc 函数的所有运行的线程 ID 始终相同。在我看来,它证明了 await 之后的代码(可以)在不同的线程中运行。这就是代码更快的原因(当然在我看来)。

Gab*_*uci 6

了解async方法的工作原理很重要。

首先,它们开始在同一个线程上同步运行,就像其他所有方法一样。如果没有该await Task.Delay(1)行,编译器将警告您该方法将是完全同步的。该async关键字不,本身,让你的方法是异步的。它只是启用await.

魔法首先发生在await作用于不完整Task. 此时该方法返回。它返回一个Task,您可以使用它来检查方法的其余部分何时完成。

因此,当您await Task.Delay(1)到达那里时,该方法在该行返回,允许您的MainAsync方法移动到下一行并开始下一次调用Calc.

如何继续Calc运行(之后的所有内容await Task.Delay(1))取决于是否存在“同步上下文”。例如,在 ASP.NET(不是 Core)或 UI 应用程序中,同步上下文控制延续的运行方式,并且它们将一个接一个地运行。在 UI 应用程序中,它将位于它开始的同一线程上。在 ASP.NET 中,它可能是一个不同的线程,但仍然一个接一个。因此,无论哪种情况,您都不会看到任何并行性。

但是,因为这是一个控制台应用程序,它没有同步上下文,一旦TaskfromTask.Delay(1)完成,continuation 就会发生在任何 ThreadPool 线程上。这意味着每个延续都可以并行发生。

另外值得注意的是:从 C# 7.1 开始,您可以创建您的Main方法async,从而无需您的MainAsync方法:

static async Task Main(string[] args)
Run Code Online (Sandbox Code Playgroud)