1:背景1.讲故事Await,async知识点已经说烂了不能再烂了,看这里好像有没什么好说的,但是我发现很多文章还是在理论上描述这两个语法糖的用法。如果你理解它,你仍然理解它。不懂的话,好像过几天就懂了。记不太清楚,其实await和async只是编译器层面的语法糖,在IL层面会原型化,所以在这个层面理解这两个语法糖是非常有必要的。二:从IL层面理解1.使用WebClient下载。为了方便返璞归真,我先举个例子,使用webclient异步下载http://cnblogs.com的html,代码如下:classProgram{staticvoidMain(string[]args){varhtml=GetResult();Console.WriteLine("稍等...正在下载cnblogs->html\r\n");varcontent=html.Result;Console.WriteLine(content);}staticasyncTaskGetResult(){varclient=newWebClient();varcontent=awaitclient.DownloadStringTaskAsync(newUri("http://cnblogs.com"));returncontent;}}上面的代码很简单,可以看到异步操作并没有阻塞主线程输出:稍等……正在下载cnblogs->html\r\n,编译器层面没什么好说的,我们来看看IL层面发生了什么?2.awaitasync的IL代码还是老规矩,ilSpy启动,如下图:可以看到,有一个GetResult方法,一个Main方法,还有一个d__1类是凭空冒出来的。接下来我就和大家一一聊聊。<1style="box-sizing:border-box;">\d__1>这个类特别有趣,因为它是凭空冒出来的,所以让我们看看它的IL长什么样?.classnestedprivateautoansaledbeforefieldinit'd__1'extends[System.Runtime]System.Objectimplements[System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine{.methodprivatefinalhidebysignewslotvirtualinstancevoidMoveNext()cilmanaged{}.methodprivatefinalhidebysignewslotvirtualinstancevoidSetStateMachine(类[System.Runtime]System.Runtime.CompilerServices.IAsyncStateMaccilinestate{}从上面托管IL代码,我们可以看到这是自动生成的实现接口IAsyncStateMachine的d__1类,定义如下:里面的MoveNext是不是很眼熟?通常,你会在foreach集合中使用这个方法,当时它被称为枚举类,这里被修改,被称为状态机😄😄😄。<2style="box-sizing:border-box;">GetResult()为了方便表现,我对方法体中的IL代码做一下简单化:.methodprivatehidebysigstaticclass[System.Runtime]System.Threading.Tasks.Task`1GetResult()cilmanaged{IL_0000:newobjinstancevoidConsoleApp3.Program/'d__1'::.ctor()IL_0005:stloc.0IL_0006:ldloc.0IL_0007:callvaluetype[System.Threading.Tasks]System.Runtime.CompilerServices。AsyncTaskMethodBuilder`1valuetype[System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Create()IL_000c:stfldvaluetype[System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1ConsoleApp3.Program/'d__1'::'<>t__builder'IL_0011:ldloc.0IL_0012:ldc.i4.m1IL_0013:stfldint32ConsoleApp3.Program/'d__1'::'<>1__state'IL_0018:ldloc.0IL_0019:ldfldavaluetype[System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1d__1'::'<>t__builder'IL_001e:ldloca.s0IL_0020:callinstancevoidvaluetype[System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Startd__1'>(!!0&)IL_0025:ldloc.0IL_0026:ldfldavaluetype[System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1ConsoleApp3.Program/'d__1'::'<>t__builder'IL_002b:callinstanceclass[System.Runtime]System.Threading.Tasks.Task`1valuetype[System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::get_Task()IL_0030:ret}//endofmethodProgram::GetResult稍微懂一点的应该知道IL_0000处的newobj在做newd__1,然后从IL_002b返回一个get_Task(),这时候,你应该明白为什么主线程不会被阻塞,因为人家返回的是Task,对吧,最终的http结果会隐藏在Task中,这样很好理解<3style="box-sizing:border-box;">MainMain方法没有任何变化,还是原来的样子。三:将IL代码重写为C#1.完整的C#代码通过前面的部分,你应该对IL层面的await和async有了一个框架的认识。这里我会把所有的C#代码写回去:=html.Result;Console.WriteLine(content);}staticTaskGetResult(){GetResultstateMachine=newGetResult();stateMachine.builder=AsyncTaskMethodBuilder.Create();stateMachine.state=-1;stateMachine。builder.Start(refstateMachine);returnstateMachine.builder.Task;}}classGetResult:IAsyncStateMachine{publicintstate;publicAsyncTaskMethodBuilderbuilder;privateWebClientclient;privatestringcontent;privatestrings3;privateTaskAwaiter等待者;publicvoidMoveNext(){varresult=string.Empty;TaskAwaiter);num=state=-1;}else{client=newWebClient();localAwaiter=client.DownloadStringTaskAsync(newUri("http://cnblogs.com")).GetAwaiter();if(!localAwaiter.IsCompleted){num=state=0;awaiter=localAwaiter;stateMachine=this;builder.AwaitUnsafeOnCompleted(reflocalAwaiter,refstateMachine);返回;}}s3=localAwaiter.GetResult();content=s3;s3=null;result=content;}catch(Exceptionexx){state=-2;client=null;content=null;builder.SetException(exx);}state=-2;client=null;content=null;builder.SetResult(result);}publicvoidSetStateMachine(IAsyncStateMachiestateMachine){}}可以看出写回C#代码后运行没有问题,对于为了便于理解,我先画一个流程图通过上面的xmind,它的基本流程是:stateMachine.builder.Start(refstateMachine)->GetResult.MoveNext->client.DownloadStringTaskAsync->localAwaiter.IsCompleted=false->builder.AwaitUnsafeOnCompleted(reflocalAwaiter,refstateMachine)->GetResult.MoveNext->localAwaiter.GetResult()->builder.SetResult(result)2.分析AsyncTaskMethodBuilder其实仔细观察就会发现,所谓的await和async异步操作都是由AsyncTaskMethodBuilder承载的,比如异步任务的启动,要编组html结果,联系底层IO,其中Task对应AsyncTaskMethodBuilder,Task对应AsyncTaskMethodBuilder,这就是为什么编译器一直提示你返回Task和Taskatasync。如果没有,就找不到对应的AsyncTaskMethodBuilder对对对,如下图:然后关注AwaitUnsafeOnCompleted方法。这个方法很重要。其注释如下:////Summary://Schedulesthestatemachinetoproceedtothenextactionwhenthespecified//awaitercompletes.Thismethodcanbecalledfrompartiallytrustedcode.publicvoidAwaitUnsafeOnCompleted<[Nullable]waitAttribute(0)NullableAttribute(0)]TStateMachine>(refTAwaiterawaiter,refTStateMachinestateMachine)whereTAwaiter:ICriticalNotifyCompletionwhereTStateMachine:IAsyncStateMachine;一旦调用该方法,需要等待底层IO完成任务,回调两次GetResult.MoveNext,要么异常,要么任务完成。Awaiter包装的任务结果被编组到builder.SetResult然后我们简单说一下状态机的运行方式。通过调试,你会发现MoveNext在这里会走两次,一次开始,一次得到结果。<1>第一次回调MoveNextMoveNext的第一次触发是由stateMachine.builder.Start(refstateMachine)发起的,可以使用dnspy调试,如下图所示:<2>MoveNext的第二次回调是MoveNext的第二次触发从builder.AwaitUnsafeOnCompleted(reflocalAwaiter,refstateMachine)开始,可以看到一旦处理完网络驱动,线程池中的IO线程就会主动发起,最终触发MoveNext代码,最后在awaiter处结束,得到任务的结果,如下图:四:总结语法糖分简单和复杂,不要怕复杂,学会翻译ILcodeintoC#,说不定很多以前不明白的东西这时候就豁然开朗了,不是吗?本文转载自微信公众号“行行码农聊技术”,可通过以下二维码关注。转载本文,请联系一线码农,聊聊技术公众号。一线码农聊天技术