await,async 我要把它翻个底朝天,这回你总该明白了吧
一:背景
1. 講故事
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 \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 層面發生了什么?
2. 挖掘 await async 的IL代碼
還是老規矩, ilSpy 走起,如下圖:
可以看到,這里有一個?GetResult?方法 ,一個?Main?方法,還有一個不知道在哪里冒出來的?<GetResult>d__1?類,接下來和大家一個一個聊。
<1 style="box-sizing: border-box;"> \d__1> 類
因為不知道從哪里冒出來的,特別引人關注,所以看看它的 IL 是咋樣的?
.class nested private auto ansi sealed beforefieldinit '<GetResult>d__1'extends [System.Runtime]System.Objectimplements [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,定義如下:
看到里面的?MoveNext?是不是很眼熟,平時你在 foreach 集合的時候就會用到這個方法,那時人家叫做枚舉類,在這里算是被改造了一下, 叫狀態機????????????。
<2 style="box-sizing: border-box;"> GetResult ()
為了方便演示,我對方法體中的 IL 代碼做一下簡化:
.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.0IL_0006: ldloc.0IL_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.0IL_0012: ldc.i4.m1IL_0013: stfld int32 ConsoleApp3.Program/'<GetResult>d__1'::'<>1__state'IL_0018: ldloc.0IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'IL_001e: ldloca.s 0IL_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.0IL_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>?中,這樣是不是就很好理解了。
<3 style="box-sizing: border-box;"> Main
Main方法沒有做任何改變,原來是什么樣現在還是什么樣。
三:將 IL 代碼 回寫為 C#
1. 完整 C# 代碼
通過前面一部分你應該對 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)
2. 剖析 AsyncTaskMethodBuilder
其實你仔細觀察會發現,所謂的 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 : ICriticalNotifyCompletionwhere 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# ,或許你以前很多不明白的地方此時都會豁然開朗,不是嗎?
總結
以上是生活随笔為你收集整理的await,async 我要把它翻个底朝天,这回你总该明白了吧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET Core实用技巧(一)如何将E
- 下一篇: 【招聘(深圳)】轻岁 诚聘.NET Co