我对 async/await、它的工作原理和好处的理解是否正确?

Is my understanding of async/await, how it works and its benefits, correct?

我曾在几个场合断言过我对 async/await 的理解,但常常会引发一些关于我是否正确的争论。如果有人能证实或否认我的理解,并澄清任何误解,以免我传播错误信息,我将不胜感激。

高层次理解

async/await 是一种在编写异步代码时避免回调地狱的方法。正在执行异步方法的线程会在遇到 await 时 return 进入线程池,并在等待的操作完成后开始执行。

低层次的理解

JIT 会将异步方法拆分为围绕 await 个点的离散部分,允许重新进入方法并保留方法的状态。在幕后,这涉及某种状态机。

与并发的关系

async/await 并不意味着任何类型的并发。使用 async/await 编写的应用程序可以完全是单线程的,同时仍然获得所有好处,就像 node.js 所做的那样,尽管有回调。与 node.js 不同,.NET 是多线程的,因此通过 async/await,您可以在不使用回调的情况下获得非阻塞 IO 的好处,同时还具有多个执行线程。

好处

async/await 在等待 IO 完成时释放线程去做其他事情。它还可以与 TPL 结合使用,以在多个线程上或在 UI 线程之外执行 CPU 绑定工作。

为了受益于非阻塞 IO,异步方法需要构建在 API 之上,实际上 利用了非阻塞其中IO最终由OS.

提供

误用

这是我理解中最大的争论点。许多人认为将阻塞操作包装在 Task 中并使用 async/await 会带来性能提升。通过创建一个额外的线程来处理操作,return将原始线程放入线程池,然后在任务完成后恢复原始方法,所发生的只是不必要的上下文切换,而没有真正释放线程去做其他工作。虽然这并不是对 async/await 的滥用,因为它是 TPL,但这种心态似乎源于对 async/await 的误解。

非常正确。

注意事项:

  • 开始执行异步方法的线程是调用者的线程,它可能是也可能不是 ThreadPool 线程。
  • 如果到达等待,但可等待(通常 Task)已经完成,线程将继续同步执行方法的其余部分。
  • 恢复运行方法的线程通常是ThreadPool线程,但这取决于SyncrhonizationContextTaskScheduler
  • 这里不涉及 JIT(不比平常多)。编译器是将异步方法转换为状态机的编译器。你可以看到 this TryRoslyn example.
  • 的确,async-await 并不一定意味着并发,因为它可能是单线程的。但是,通过同时启动和等待多个异步操作,即使只有一个线程,它仍然可以并发。
  • async-await 和 TPL 不是完全独立的部分。 async-await 建立在 TPL 之上。这就是为什么它被称为基于任务的异步模式。
  • 虽然大多数真正的异步操作是 I/O,但并非所有都是。您通常还使用 Task.Delay 异步延迟或使用 SemaphoreSlim.WaitAsync.
  • 等异步同步结构