我对 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
线程,但这取决于SyncrhonizationContext
和TaskScheduler
。
- 这里不涉及 JIT(不比平常多)。编译器是将异步方法转换为状态机的编译器。你可以看到 this TryRoslyn example.
- 的确,async-await 并不一定意味着并发,因为它可能是单线程的。但是,通过同时启动和等待多个异步操作,即使只有一个线程,它仍然可以并发。
- async-await 和 TPL 不是完全独立的部分。 async-await 建立在 TPL 之上。这就是为什么它被称为基于任务的异步模式。
- 虽然大多数真正的异步操作是 I/O,但并非所有都是。您通常还使用
Task.Delay
异步延迟或使用 SemaphoreSlim.WaitAsync
. 等异步同步结构
我曾在几个场合断言过我对 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
线程,但这取决于SyncrhonizationContext
和TaskScheduler
。 - 这里不涉及 JIT(不比平常多)。编译器是将异步方法转换为状态机的编译器。你可以看到 this TryRoslyn example.
- 的确,async-await 并不一定意味着并发,因为它可能是单线程的。但是,通过同时启动和等待多个异步操作,即使只有一个线程,它仍然可以并发。
- async-await 和 TPL 不是完全独立的部分。 async-await 建立在 TPL 之上。这就是为什么它被称为基于任务的异步模式。
- 虽然大多数真正的异步操作是 I/O,但并非所有都是。您通常还使用
Task.Delay
异步延迟或使用SemaphoreSlim.WaitAsync
. 等异步同步结构