Async/Await 行为通过堆栈

Async/Await behaviour through the stack

我很好奇 async 的流程是如何跨堆栈工作的。在阅读 C# 中的 async 时,您将不断阅读以下内容的某些版本:

If the task we are awaiting has not yet completed then sign up the rest of this method as the continuation of that task, and then return to your caller immediately; the task will invoke the continuation when it completes.

让我感到困惑的是 return 立即给您的来电者 部分。在下面的示例中,假设某些调用 MethodOneAsync(),执行命中 MethodOneAsync 中的等待。它会立即 return 到调用它的方法吗?如果是这样,MethodTwoAsync 是如何被执行的?或者它是否继续向下堆栈直到它检测到它实际上被阻塞(即。DoDBWorkAsync())然后让出线程?

public static async Task MethodOneAsync()
{
    DoSomeWork();
    await MethodTwoAsync();
}

public static async Task MethodTwoAsync()
{
    DoSomeWork();
    await MethodThreeAsync();
}

public static async Task MethodThreeAsync()
{
    DoSomeWork();
    await DoDBWorkAsync();
}

async方法中await之前的部分同步执行。所有 async 方法都是如此。

让我们假设 await DoDBWorkAsync() 我们有 await Task.Delay(1000).

这意味着 MethodOneAsync 启动 运行ning,执行 DoSomeWork 并调用 MethodTwoAsync,然后执行 DoSomeWork,然后调用 MethodThreeAsync再次执行 DoSomeWork.

然后它调用 Task.Delay(1000),返回一个未完成的 Task 并等待它。

await 在逻辑上等同于添加一个延续并 return 将任务返回给调用者,MethodTwoAsync 做同样的事情 return Task 给来电者等等。

这样当根延迟Task完成所有的延续可以运行一个接一个


如果我们让你的例子更复杂一点:

public static async Task MethodOneAsync()
{
    DoSomeWorkOne();
    await MethodTwoAsync();
    DoMoreWorkOne();
}

public static async Task MethodTwoAsync()
{
    DoSomeWorkTwo();
    await MethodThreeAsync();
    DoMoreWorkTwo();
}

public static async Task MethodThreeAsync()
{
    DoSomeWorkThree();
    await Task.Delay(1000);
    DoMoreWorkThree();
}

这在逻辑上类似于使用延续来执行此操作:

public static Task MethodOneAsync()
{
    DoSomeWorkOne();
    DoSomeWorkTwo();
    DoSomeWorkThree();
    return Task.Delay(1000).
        ContinueWith(_ => DoMoreWorkThree()).
        ContinueWith(_ => DoMoreWorkTwo()).
        ContinueWith(_ => DoMoreWorkOne());
}

它 return MethodTwoAsync 方法触发 Task 之后(或继续执行,如果此 Task 是立即完成),因此内部 Task 正在执行到 SynchronizationContext.Current 环境中。

Task 完成后,.NET 状态机 return 执行到 MethodTwoAsync 触发后的点,并处理其余代码。

MSDN article 上查看更多信息:

首先,让我做一个不同的例子,这样我的代码就更有意义了

public static async Task MethodOneAsync()
{
    DoSomeWork1();
    await MethodTwoAsync();
    DoOtherWork1();
}

public static async Task MethodTwoAsync()
{
    DoSomeWork2();
    await MethodThreeAsync();
    DoOtherWork2();
}

public static async Task MethodThreeAsync()
{
    DoSomeWork3();
    await DoDBWorkAsync();
    DoOtheWork3();
}

所有 async await 所做的就是将上面的代码变成类似于(但即使这是一个巨大的简化)this

public static Task MethodOneAsync()
{
    DoSomeWork1();
    var syncContext = SynchronizationContext.Current ?? new SynchronizationContext();
    var resultTask = MethodTwoAsync();
    return resultTask.ContinueWith((task) =>
    {
        syncContext.Post((state) =>
        {
            SynchronizationContext.SetSynchronizationContext(syncContext);
            DoOtherWork1();
        }, null);
    });
}

public static Task MethodTwoAsync()
{
    DoSomeWork2();
    var syncContext = SynchronizationContext.Current ?? new SynchronizationContext();
    var resultTask = MethodThreeAsync();
    return resultTask.ContinueWith((task) =>
    {
        syncContext.Post((state) =>
        {
            SynchronizationContext.SetSynchronizationContext(syncContext);
            DoOtherWork2();
        }, null);
    });
}

public static Task MethodThreeAsync()
{
    DoSomeWork3();
    var syncContext = SynchronizationContext.Current ?? new SynchronizationContext();
    var resultTask = DoDbWorkAsync();
    return resultTask.ContinueWith((task) =>
    {
        syncContext.Post((state) =>
        {
            SynchronizationContext.SetSynchronizationContext(syncContext);
            DoOtherWork3();
        }, null);
    });
}.

每个 await 只执行更深的下一层,直到它被迫 return 一个任务,一旦发生这种情况,它就会在任务完成时开始对任务进行延续,并在该延续中传递一个代表 "the rest of the function" 到 SynchronizationContext.Post( 以获得计划执行的代码。

它的调度方式取决于你所在的 SynchronizationContext。在 WPF 和 Winforms 中,默认情况下它将它排到消息泵中,ASP.NET 它在线程池工作线程中将它排到队列中.