请从底层实现机制的角度,说明 C# 中 async/await 是如何工作的?
考察说明
考查对 C# 异步编程模型底层原理的理解,包括状态机、同步上下文和任务调度。
回答思路
- 【回答框架 1】async/await 是编译器层面的语法糖,核心是生成一个状态机。当遇到 await 时,方法被拆分为多个状态,每次 await 都会保存当前执行位置和局部变量,并注册一个延续,即状态机的 MoveNext 方法。
- 【回答框架 2】状态机实现了 IAsyncStateMachine 接口,包含状态字段和构建器。编译器将方法体转换为一个结构体或类,通过 AsyncTaskMethodBuilder 来管理任务的创建和完成,以及异常的捕获和传播。
- 【回答框架 3】await 表达式会检查任务是否已完成,若已完成则直接继续执行;否则,会将状态机注册到任务的延续中,并返回一个不完整的任务给调用者,使当前线程可以继续执行其他工作。
- 【回答框架 4】任务完成时,延续会被调度执行。调度器默认使用当前同步上下文,若为 null 则使用线程池。这允许在 UI 线程上捕获上下文并在完毕后回到该线程,但并非总是如此,实际行为取决于上下文。
- 【回答框架 5】整个机制涉及异步方法的返回类型、可等待模式、线程上下文以及任务调度的协调,最终实现非阻塞的异步操作,但具体实现细节随版本可能有所变化。
- 【关键点 1】编译器将 async 方法转换为状态机,每个 await 点对应一个状态迁移。
- 【关键点 2】状态机保存局部变量并注册延续,避免阻塞线程。
- 【关键点 3】延续默认通过同步上下文或线程池调度,决定恢复线程。
- 【关键点 4】AsyncTaskMethodBuilder 管理任务状态和异常传播。
- 【易错点 1】异步方法不一定在另一个线程执行,await 本身不创建新线程。
- 【易错点 2】死锁可能因在同步上下文中阻塞异步方法而发生,如 UI 线程调用 Wait。
- 【易错点 3】延续调度的上下文不一定与起始相同,需明确同步上下文行为。