await,async 这玩意的知识点已经被人说的烂的不能再烂了,看似没什么好说的,但我发现有很多文章仍是从理论上讲述了这两个语法糖的用法,懂得仍是懂,不懂的看似懂了过几天又不懂了,人生如戏全靠记是不行的哈😄😄😄,其实本质上来讲 await, async 只是编译器层面上的语法糖,在 IL 层面都会被打成原型的,因此在这个层面上认识这两个语法糖是很是有必要的。html
为了方便打回原型,我先上一个例子,使用 webclient 异步下载 http://cnblogs.com
的html,代码以下:web
class Program { static void Main(string[] args) { var html = GetResult(); Console.WriteLine("稍等... 正在下载 cnblogs -> html \r\n"); var content = html.Result; Console.WriteLine(content); } static async Task<string> GetResult() { var client = new WebClient(); var content = await client.DownloadStringTaskAsync(new Uri("http://cnblogs.com")); return content; } }
上面的代码很是简单,能够看到异步操做没有阻塞主线程输出: 稍等... 正在下载 cnblogs -> html \r\n
, 编译器层面没什么好说的 ,接下来看下在 IL 层面发生了什么?网络
仍是老规矩, ilSpy 走起,以下图:框架
能够看到,这里有一个 GetResult
方法 ,一个 Main
方法,还有一个不知道在哪里冒出来的 <GetResult>d__1
类,接下来和你们一个一个聊。异步
由于不知道从哪里冒出来的,特别引人关注,因此看看它的 IL 是咋样的?async
.class nested private auto ansi sealed beforefieldinit '<GetResult>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 代码能够看到,这是自动生成的 <GetResult>d__1
类实现了接口 IAsyncStateMachine
,定义以下:ide
看到里面的 MoveNext
是否是很眼熟,平时你在 foreach 集合的时候就会用到这个方法,那时人家叫作枚举类,在这里算是被改造了一下, 叫状态机😄😄😄。ui
为了方便演示,我对方法体中的 IL 代码作一下简化:this
.method private hidebysig static class [System.Runtime]System.Threading.Tasks.Task`1<string> GetResult () cil managed { IL_0000: newobj instance void ConsoleApp3.Program/'<GetResult>d__1'::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Create() IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder' IL_0011: ldloc.0 IL_0012: ldc.i4.m1 IL_0013: stfld int32 ConsoleApp3.Program/'<GetResult>d__1'::'<>1__state' IL_0018: ldloc.0 IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder' IL_001e: ldloca.s 0 IL_0020: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Start<class ConsoleApp3.Program/'<GetResult>d__1'>(!!0&) IL_0025: ldloc.0 IL_0026: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder' IL_002b: call instance class [System.Runtime]System.Threading.Tasks.Task`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::get_Task() IL_0030: ret } // end of method Program::GetResult
若是你稍微懂一点的话,在 IL_0000
处的 newobj 你就应该知道这个方法就是作了 new <GetResult>d__1
,而后从 IL_002b
处返回了一个 get_Task()
,这时候你就应该明白,为何主线程不会被阻塞,由于人家返回的是 Task<string>
,对吧,最后的 http 结果会藏在 Task<string>
中,这样是否是就很好理解了。spa
Main方法没有作任何改变,原来是什么样如今仍是什么样。
经过前面一部分你应该对 await ,async 在 IL 层面有了一个框架性的认识,这里我就所有反写成 C# 代码:
class Program { static void Main(string[] args) { var html = GetResult(); Console.WriteLine("稍等... 正在下载 cnblogs -> html \r\n"); var content = html.Result; Console.WriteLine(content); } static Task<string> GetResult() { GetResult stateMachine = new GetResult(); stateMachine.builder = AsyncTaskMethodBuilder<string>.Create(); stateMachine.state = -1; stateMachine.builder.Start(ref stateMachine); return stateMachine.builder.Task; } } class GetResult : IAsyncStateMachine { public int state; public AsyncTaskMethodBuilder<string> builder; private WebClient client; private string content; private string s3; private TaskAwaiter<string> awaiter; public void MoveNext() { var result = string.Empty; TaskAwaiter<string> localAwaiter; GetResult stateMachine; int num = state; try { if (num == 0) { localAwaiter = awaiter; awaiter = default(TaskAwaiter<string>); 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)
其实你仔细观察会发现,所谓的 await,async 的异步化运做都是由 AsyncTaskMethodBuilder 承载的,如异步任务的启动,对html结果的封送,接触底层IO,其中 Task<string>
对应着 AsyncTaskMethodBuilder<string>
, Task 对应着 AsyncTaskMethodBuilder, 这也是为何编译器在 async 处一直提示你返回 Task 和 Task<string>
,若是不这样的话的就找不到对应 AsyncTaskMethodBuilder 了,对吧,以下图:
而后着重看下 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,一次启动,一次拿结果。
第一次 MoveNext 的触发由 stateMachine.builder.Start(ref stateMachine) 发起,能够用 dnspy 去调试一下,以下图:
第二次 MoveNext 的触发由 builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine) 开始,能够看到一旦 网络驱动程序 处理完毕后就由线程池IO线程主动发起到最后触发代码中的 MoveNext,最后就是到 awaiter 中获取 task 的 result 处结束,以下图:
语法糖有简单和复杂之分,复杂的也不要怕,学会将 IL 代码翻译成 C# ,或许你之前不少不明白的地方此时都会豁然开朗,不是吗?