一次Async/await 原理探索
前言
本文记录一次对 C# 中 async/await
异步编程机制的原理探索过程。异步编程的实现机制较为复杂,本文旨在通过实际代码及反编译分析,对其运行逻辑进行初步梳理和理解,供参考和学习使用。
一、前置示例
首先,通过一个简单的控制台应用演示 async/await
的基本用法:
-
编写一个控制台应用。代码如下
internal class Program { static async Task Main(string[] args) { await RequestGeogle(); // 异步执行 Console.ReadLine(); // 阻塞主线程,观察结果 } public static async Task<string> RequestGeogle() { using var client = new HttpClient(); var response = await client.GetAsync("https://www.google.com"); // 第一次 await:发起网络请求 var content = await response.Content.ReadAsStringAsync(); // 第二次 await:读取响应内容 return content; } }
使用反编译工具查看编译后代码的核心部分:
[DebuggerStepThrough] private static void <Main>(string[] args) { Program.Main(args).GetAwaiter().GetResult(); } [DebuggerStepThrough] private static Task Main(string[] args) { Program.<Main>d__0 stateMachine = new Program.<Main>d__0(); stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create(); stateMachine.args = args; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; }
二、编译器所做的转换分析
-
通过分析可以看出,编译器对异步方法进行了如下处理:
1. 生成状态机类
编译器会为每一个
async
方法生成一个密封状态机类(如<Main>d__0
),实现接口IAsyncStateMachine
。其命名通常不规范,目的是避免与用户代码发生命名冲突。public interface IAsyncStateMachine { void MoveNext(); void SetStateMachine(IAsyncStateMachine stateMachine); }
-
管理状态与上下文
该状态机类负责保存方法的局部变量与异步状态,状态由
<>1__state
字段控制:-1
表示初始状态;0
,1
,2
… 分别表示不同await
点;- `-n 表示方法已结束(成功或异常),取决于这个方法有几个await。
同时,编译器使用
AsyncTaskMethodBuilder
来构建Task
返回值,并负责控制方法生命周期,如启动、挂起、恢复和异常处理。 -
启动状态机
通过
AsyncTaskMethodBuilder.Start
启动状态机,其本质调用了:public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { AsyncMethodBuilderCore.Start(ref stateMachine); }
AsyncMethodBuilderCore
可以视作控制器,其核心职责包括:- 注册恢复执行的回调
- 捕获并传播异常
- 管理线程上下文(如
SynchronizationContext
) - 控制异步任务的延续(continuation)执行方式
三、关键方法:MoveNext 解构
状态机的核心执行逻辑集中在 MoveNext()
方法中。该方法负责在异步操作挂起与恢复之间切换执行状态。
以下是一个简化后的 MoveNext()
解构分析:
void IAsyncStateMachine.MoveNext()
{
int state = this.<>1__state;
try
{
TaskAwaiter<string> awaiter1;
TaskAwaiter<string> awaiter2;
if (state != 0)
{
if (state == 1)
{
awaiter1 = this.<>u__1;
this.<>u__1 = default;
this.<>1__state = -1;
goto CONTINUE_SECOND_AWAIT;
}
awaiter2 = Program.RequestGeogle().GetAwaiter();
if (!awaiter2.IsCompleted)
{
this.<>1__state = 0;
this.<>u__1 = awaiter2;
this.<>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref this);
return;
}
}
else
{
awaiter2 = this.<>u__1;
this.<>u__1 = default;
this.<>1__state = -1;
}
awaiter2.GetResult(); // 第一次 await 完成后继续执行
awaiter1 = Program.RequestGeogle().GetAwaiter();
if (!awaiter1.IsCompleted)
{
this.<>1__state = 1;
this.<>u__1 = awaiter1;
this.<>t__builder.AwaitUnsafeOnCompleted(ref awaiter1, ref this);
return;
}
CONTINUE_SECOND_AWAIT:
string result = awaiter1.GetResult(); // 第二次 await 完成
Console.ReadLine(); // 执行剩余同步逻辑
}
catch (Exception ex)
{
this.<>1__state = -2;
this.<>t__builder.SetException(ex);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult();
}
四、状态机制总结
- 状态机通过
<>1__state
字段控制方法的进度,每个await
语句对应一个状态。 - 异步操作挂起时,使用
AwaitUnsafeOnCompleted
注册回调以在完成时恢复状态机执行(不涉及任何线程操作,仅挂起当前代码的上下文,空出CPU等待异步完成的回调信号)。 - 所有本地变量和
TaskAwaiter
都被封装进状态机类中,确保挂起后上下文可以完整恢复。 - 编译器生成的代码高度优化,最大限度保证性能,兼顾异常传播与上下文一致性。