await,async 这玩意的知识点已经被人说的烂的不能再烂了,看似没什么好说的,但我发现有不少文章还是从理论上讲述了这两个语法糖的用法,懂得还是懂,不懂的看似懂了过几天又不懂了,人生如戏全靠记是不行的哈,其实本质上来说 await, async 只是编译器层面上的语法糖,在 IL 层面都会被打成原型的,所以在这个层面上认识这两个语法糖是非常有必要的。
二:从 IL 层面认识 1. 使用 WebClient 下载为了方便打回原型,我先上一个例子,使用 webclient 异步下载 http://cnblogs.com 的html,代码如下:
class Program
{
static void Main(string[] args)
{
var html = GetResult();
Console.WriteLine("稍等... 正在下载 cnblogs -> html rn");
var content = html.Result;
Console.WriteLine(content);
}
static async Task GetResult()
{
var client = new WebClient();
var content = await client.DownloadStringTaskAsync(new Uri("http://cnblogs.com"));
return content;
}
}
上面的代码非常简单,可以看到异步操作没有阻塞主线程输出: 稍等... 正在下载 cnblogs -> html rn, 编译器层面没什么好说的 ,接下来看下在 IL 层面发生了什么?
2. 挖掘 await async 的IL代码还是老规矩, ilSpy 走起,如下图:
可以看到,这里有一个 GetResult 方法 ,一个 Main 方法,还有一个不知道在哪里冒出来的
因为不知道从哪里冒出来的,特别引人关注,所以看看它的 IL 是咋样的?
.class nested private auto ansi sealed beforefieldinit 'd__1'
extends [System.Runtime]System.Object
implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
{
.method private final hidebysig newslot virtual
instance void MoveNext () cil managed
{
}
.method private final hidebysig newslot virtual
instance void SetStateMachine (
class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
) cil managed
{
}
}
从上面的 IL 代码可以看到,这是自动生成的
看到里面的 MoveNext 是不是很眼熟,平时你在 foreach 集合的时候就会用到这个方法,那时人家叫做枚举类,在这里算是被改造了一下, 叫状态机。
<2> GetResult ()为了方便演示,我对方法体中的 IL 代码做一下简化:
.method private hidebysig static
class [System.Runtime]System.Threading.Tasks.Task`1 GetResult () cil managed
{
IL_0000: newobj instance void ConsoleApp3.Program/'d__1'::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Create()
IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 ConsoleApp3.Program/'d__1'::'<>t__builder'
IL_0011: ldloc.0
IL_0012: ldc.i4.m1
IL_0013: stfld int32 ConsoleApp3.Program/'d__1'::'<>1__state'
IL_0018: ldloc.0
IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 ConsoleApp3.Program/'d__1'::'<>t__builder'
IL_001e: ldloca.s 0
IL_0020: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Startd__1'>(!!0&)
IL_0025: ldloc.0
IL_0026: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 ConsoleApp3.Program/'d__1'::'<>t__builder'
IL_002b: call instance class [System.Runtime]System.Threading.Tasks.Task`1 valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::get_Task()
IL_0030: ret
} // end of method Program::GetResult
如果你稍微懂一点的话,在 IL_0000 处的 newobj 你就应该知道这个方法就是做了 new
Main方法没有做任何改变,原来是什么样现在还是什么样。
三:将 IL 代码 回写为 C# 1. 完整 C# 代码通过前面一部分你应该对 await ,async 在 IL 层面有了一个框架性的认识,这里我就全部反写成 C# 代码:
class Program
{
static void Main(string[] args)
{
var html = GetResult();
Console.WriteLine("稍等... 正在下载 cnblogs -> html rn");
var content = html.Result;
Console.WriteLine(content);
}
static Task GetResult()
{
GetResult stateMachine = new GetResult();
stateMachine.builder = AsyncTaskMethodBuilder.Create();
stateMachine.state = -1;
stateMachine.builder.Start(ref stateMachine);
return stateMachine.builder.Task;
}
}
class GetResult : IAsyncStateMachine
{
public int state;
public AsyncTaskMethodBuilder builder;
private WebClient client;
private string content;
private string s3;
private TaskAwaiter awaiter;
public void MoveNext()
{
var result = string.Empty;
TaskAwaiter localAwaiter;
GetResult stateMachine;
int num = state;
try
{
if (num == 0)
{
localAwaiter = awaiter;
awaiter = default(TaskAwaiter);
num = state = -1;
}
else
{
client = new WebClient();
localAwaiter = client.DownloadStringTaskAsync(new Uri("http://cnblogs.com")).GetAwaiter();
if (!localAwaiter.IsCompleted)
{
num = state = 0;
awaiter = localAwaiter;
stateMachine = this;
builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine);
return;
}
}
s3 = localAwaiter.GetResult();
content = s3;
s3 = null;
result = content;
}
catch (Exception exx)
{
state = -2;
client = null;
content = null;
builder.SetException(exx);
}
state = -2;
client = null;
content = null;
builder.SetResult(result);
}
public void SetStateMachine(IAsyncStateMachine stateMachine) { }
}
可以看到,回写成 C# 代码之后跑起来是没有任何问题的,为了方便理解,我先来画一张流程图。
通过上面的 xmind,它基本流程就是: stateMachine.builder.Start(ref stateMachine) -> GetResult.MoveNext -> client.DownloadStringTaskAsync -> localAwaiter.IsCompleted = false -> builder.AwaitUnsafeonCompleted(ref localAwaiter, ref stateMachine) -> GetResult.MoveNext -> localAwaiter.GetResult() -> builder.SetResult(result)
2. 剖析 AsyncTaskMethodBuilder其实你仔细观察会发现,所谓的 await,async 的异步化运作都是由 AsyncTaskMethodBuilder 承载的,如异步任务的启动,对html结果的封送,接触底层IO,其中 Task
然后着重看下 AwaitUnsafeOnCompleted 方法,这个方法非常重要,其注释如下:
//
// Summary:
// Schedules the state machine to proceed to the next action when the specified
// awaiter completes. This method can be called from partially trusted code.
public void AwaitUnsafeOnCompleted<[NullableAttribute(0)] TAwaiter, [NullableAttribute(0)] TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine;
一旦调用了这个方法,就需要等待 底层IO 将任务处理完毕之后二次回调 GetResult.MoveNext,也就表示要么异常要么完成任务, Awaiter 包装的 Task 结果封送到 builder.SetResult。
然后简单说一下 状态机 的走法,通过调试会发现这里会走 两次 MoveNext,一次启动,一次拿结果。
<1> 第一次回调 MoveNext第一次 MoveNext 的触发由 stateMachine.builder.Start(ref stateMachine) 发起,可以用 dnspy 去调试一下,如下图:
<2> 第二次回调 MoveNext第二次 MoveNext 的触发由 builder.AwaitUnsafeonCompleted(ref localAwaiter, ref stateMachine) 开始,可以看到一旦 网络驱动程序 处理完毕后就由线程池IO线程主动发起到最后触发代码中的 MoveNext,最后就是到 awaiter 中获取 task 的 result 处结束,如下图:
四: 总结语法糖有简单和复杂之分,复杂的也不要怕,学会将 IL 代码翻译成 C# ,或许你以前很多不明白的地方此时都会豁然开朗,不是吗?



