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>
中,這樣是否是就很好理解了。線程
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# ,或許你之前不少不明白的地方此時都會豁然開朗,不是嗎?