async方法被编译为实现iasyncstatemachine的私有状态机类(或结构体),含局部变量、state字段及movenext方法;await不切换线程,而是通过oncompleted注册movenext回调驱动状态恢复。

async 方法会被编译成状态机类
C# 编译器(Roslyn)遇到 async 方法时,不会生成普通方法体,而是重写为一个实现了 IAsyncStateMachine 接口的私有嵌套结构体(或类,取决于是否捕获局部变量),这个类型就是“状态机”。它包含所有局部变量、参数、await 点的暂存字段,以及 MoveNext() 方法——所有 await 逻辑都收束到这个方法里。
常见误区是以为 await 直接调用线程切换,其实它只是在状态机中记录当前执行位置(state 字段),然后返回一个未完成的 Task,后续靠 GetAwaiter().OnCompleted() 注册回调来驱动状态机继续执行。
- 状态机字段名如
1__state、t__builder、<paramname>5__1</paramname>都是编译器自动生成,不可见但可通过反编译(如 ILSpy)观察 - 如果方法不包含
await,编译器仍会生成状态机,但会优化为同步完成(state = -2表示已完成) - 捕获
this或局部引用类型变量时,状态机会升格为class(避免装箱/生命周期问题),值类型参数则按需拷贝
Builder 模式控制 Task 构建与完成
每个 async 方法开头都会初始化一个 AsyncTaskMethodBuilder<tresult></tresult>(或 AsyncVoidMethodBuilder),它封装了 Task 的创建、异常传播、同步/异步完成逻辑。真正的 Task 实例直到第一次 await 或方法退出才被真正构造或设置结果。
关键点在于:状态机不直接 new Task,而是通过 builder 的 SetResult()、SetException()、SetStateMachine() 等方法间接控制任务生命周期。
-
SetStateMachine(this)必须在MoveNext()首次调用前执行,否则Task无法正确绑定状态机 - 对于
async void,使用AsyncVoidMethodBuilder,异常会直接抛给同步上下文(SynchronizationContext),无法被外层try/catch捕获 - builder 的
Task属性在未完成前返回的是懒构造的占位符,不是真实运行时Task
await 表达式展开为 GetAwaiter + OnCompleted + UnsafeOnCompleted
await 不是语法糖,而是一组确定的模式调用:编译器先调用表达式的 GetAwaiter(),再检查返回值是否实现 INotifyCompletion(常用 TaskAwaiter),然后插入 OnCompleted(action) 或更底层的 UnsafeOnCompleted(action) 注册回调。
这个回调函数正是状态机的 MoveNext(),它被包装进委托并传入。当 awaitable 完成时,该委托被调度执行,状态机从上次暂停的 state 值继续运行。
- 如果 awaitable 已完成(
IsCompleted == true),编译器可能跳过注册,直接同步执行后续代码(“快速路径”) -
UnsafeOnCompleted绕过栈探测和委托分配,在 hot path 上性能更高,但要求回调必须是无栈捕获的纯状态机调用 - 自定义 awaitable 必须提供
GetAwaiter()+IsCompleted+OnCompleted()/UnsafeOnCompleted()+GetResult()才能被await消费
调试时看到的“假堆栈”和状态机实例泄漏
在调试器中单步进入 async 方法,看到的堆栈常含 MoveNext 和大量 <xxx>d__N</xxx> 类型,这就是状态机实例。它生命周期往往长于方法调用本身——比如 await 一个网络请求时,状态机实例会一直存活,直到响应返回并执行完后续逻辑。
这意味着:状态机字段若持有大对象(如 byte[]、Stream),且未显式置空,就可能造成内存滞留;同时,调试器显示的“当前行”其实是状态机里的 goto 标签位置,不是原始源码顺序。
- 反编译后可见
switch(state)块,每个case对应一个 await 暂停点之后的恢复位置 - 状态机实例默认不实现
IDisposable,无法用using释放,需靠 GC 回收;若需提前清理,应在finally块中手动清空大字段 - 在高并发短生命周期场景(如 ASP.NET Core 中间件),过多状态机分配会影响 GC 压力,此时可考虑
ValueTask+ 同步完成优化










