等待运算符和 async 关键字

await 运算符和 async 关键字汇集在一起​​:

必须使用 async 关键字修改使用 await 的异步方法。 ****

相反的情况并非总是如此:你可以在不使用 await 的情况下将方法标记为 async

await 实际上做的是暂停执行代码,直到等待的任务完成; 任何任务都可以等待。

注意: 你不能等待没有返回任何内容的异步方法(void)。

实际上,暂停这个词有点误导,因为不仅执行停止,而且线程可以自由执行其他操作。在引擎盖下,await 由一些编译器魔术实现:它将一个方法分为两部分 - 在 await 之前和之后。当等待的任务完成时执行后一部分。

如果我们忽略一些重要的细节,编译器大致会为你做这件事:

public async Task<TResult> DoIt()
{
    // do something and acquire someTask of type Task<TSomeResult>  
    var awaitedResult = await someTask;
    // ... do something more and produce result of type TResult
    return result;
}

变为:

public Task<TResult> DoIt()
{
    // ...
    return someTask.ContinueWith(task => {
        var result = ((Task<TSomeResult>)task).Result;
        return DoIt_Continuation(result);
    });
}

private TResult DoIt_Continuation(TSomeResult awaitedResult)
{
    // ...
}

可以通过以下方式将任何常用方法转换为异步:

await Task.Run(() => YourSyncMethod());

当你需要在 UI 线程上执行长时间运行的方法而不冻结 UI 时,这可能是有利的。

但是这里有一个非常重要的评论: 异步并不总是意味着并发(并行甚至多线程)。即使在单个线程上,async-await 仍然允许异步代码。例如,请参阅此自定义任务计划程序 。这种疯狂的任务调度程序可以简单地将任务转换为在消息循环处理中调用的函数。

我们需要问自己:什么线程将执行我们的方法 DoIt_Continuation 的延续?

默认情况下,await 运算符使用当前的 Synchronization 上下文调度 continuation 的执行。这意味着默认情况下,WinForms 和 WPF 延续在 UI 线程中运行。如果由于某种原因需要更改此行为,请使用方法 Task.ConfigureAwait()

await Task.Run(() => YourSyncMethod()).ConfigureAwait(continueOnCapturedContext: false);