新聞中心
一:背景

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡(jiǎn)單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:主機(jī)域名、網(wǎng)頁空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、黎川網(wǎng)站維護(hù)、網(wǎng)站推廣。
1. 講故事
await,async 這玩意的知識(shí)點(diǎn)已經(jīng)被人說的爛的不能再爛了,看似沒什么好說的,但我發(fā)現(xiàn)有不少文章還是從理論上講述了這兩個(gè)語法糖的用法,懂得還是懂,不懂的看似懂了過幾天又不懂了,人生如戲全靠記是不行的哈,其實(shí)本質(zhì)上來說 await, async 只是編譯器層面上的語法糖,在 IL 層面都會(huì)被打成原型的,所以在這個(gè)層面上認(rèn)識(shí)這兩個(gè)語法糖是非常有必要的。
二:從 IL 層面認(rèn)識(shí)
1. 使用 WebClient 下載
為了方便打回原型,我先上一個(gè)例子,使用 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
GetResult() - {
- var client = new WebClient();
- var content = await client.DownloadStringTaskAsync(new Uri("http://cnblogs.com"));
- return content;
- }
- }
上面的代碼非常簡(jiǎn)單,可以看到異步操作沒有阻塞主線程輸出: 稍等... 正在下載 cnblogs -> html \r\n, 編譯器層面沒什么好說的 ,接下來看下在 IL 層面發(fā)生了什么?
2. 挖掘 await async 的IL代碼
還是老規(guī)矩, ilSpy 走起,如下圖:
可以看到,這里有一個(gè) GetResult 方法 ,一個(gè) Main 方法,還有一個(gè)不知道在哪里冒出來的 d__1 類,接下來和大家一個(gè)一個(gè)聊。
<1 style="box-sizing: border-box;"> \d__1> 類
因?yàn)椴恢缽哪睦锩俺鰜淼?,特別引人關(guān)注,所以看看它的 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 代碼可以看到,這是自動(dòng)生成的 d__1 類實(shí)現(xiàn)了接口 IAsyncStateMachine,定義如下:
看到里面的 MoveNext 是不是很眼熟,平時(shí)你在 foreach 集合的時(shí)候就會(huì)用到這個(gè)方法,那時(shí)人家叫做枚舉類,在這里算是被改造了一下, 叫狀態(tài)機(jī)。
<2 style="box-sizing: border-box;"> GetResult ()
為了方便演示,我對(duì)方法體中的 IL 代碼做一下簡(jiǎn)化:
- .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
::Start d__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
如果你稍微懂一點(diǎn)的話,在 IL_0000 處的 newobj 你就應(yīng)該知道這個(gè)方法就是做了 new d__1,然后從 IL_002b 處返回了一個(gè) get_Task() ,這時(shí)候你就應(yīng)該明白,為什么主線程不會(huì)被阻塞,因?yàn)槿思曳祷氐氖?Task ,對(duì)吧,最后的 http 結(jié)果會(huì)藏在 Task 中,這樣是不是就很好理解了。
<3 style="box-sizing: border-box;"> Main
Main方法沒有做任何改變,原來是什么樣現(xiàn)在還是什么樣。
三:將 IL 代碼 回寫為 C#
1. 完整 C# 代碼
通過前面一部分你應(yīng)該對(duì) await ,async 在 IL 層面有了一個(gè)框架性的認(rèn)識(shí),這里我就全部反寫成 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
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
其實(shí)你仔細(xì)觀察會(huì)發(fā)現(xiàn),所謂的 await,async 的異步化運(yùn)作都是由 AsyncTaskMethodBuilder 承載的,如異步任務(wù)的啟動(dòng),對(duì)html結(jié)果的封送,接觸底層IO,其中 Task 對(duì)應(yīng)著 AsyncTaskMethodBuilder , Task 對(duì)應(yīng)著 AsyncTaskMethodBuilder, 這也是為什么編譯器在 async 處一直提示你返回 Task 和 Task ,如果不這樣的話的就找不到對(duì)應(yīng) AsyncTaskMethodBuilder 了,對(duì)吧,如下圖:
然后著重看下 AwaitUnsafeOnCompleted 方法,這個(gè)方法非常重要,其注釋如下:
- //
- // 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;
一旦調(diào)用了這個(gè)方法,就需要等待 底層IO 將任務(wù)處理完畢之后二次回調(diào) GetResult.MoveNext,也就表示要么異常要么完成任務(wù), Awaiter 包裝的 Task 結(jié)果封送到 builder.SetResult。
然后簡(jiǎn)單說一下 狀態(tài)機(jī) 的走法,通過調(diào)試會(huì)發(fā)現(xiàn)這里會(huì)走 兩次 MoveNext,一次啟動(dòng),一次拿結(jié)果。
<1> 第一次回調(diào) MoveNext
第一次 MoveNext 的觸發(fā)由 stateMachine.builder.Start(ref stateMachine) 發(fā)起,可以用 dnspy 去調(diào)試一下,如下圖:
<2> 第二次回調(diào) MoveNext
第二次 MoveNext 的觸發(fā)由 builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine) 開始,可以看到一旦 網(wǎng)絡(luò)驅(qū)動(dòng)程序 處理完畢后就由線程池IO線程主動(dòng)發(fā)起到最后觸發(fā)代碼中的 MoveNext,最后就是到 awaiter 中獲取 task 的 result 處結(jié)束,如下圖:
四:總結(jié)
語法糖有簡(jiǎn)單和復(fù)雜之分,復(fù)雜的也不要怕,學(xué)會(huì)將 IL 代碼翻譯成 C# ,或許你以前很多不明白的地方此時(shí)都會(huì)豁然開朗,不是嗎?
本文轉(zhuǎn)載自微信公眾號(hào)「一線碼農(nóng)聊技術(shù)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系一線碼農(nóng)聊技術(shù)公眾號(hào)。 一線碼農(nóng)聊技術(shù)
網(wǎng)頁名稱:Await,Async 我要把它翻個(gè)底朝天,這回你總該明白了吧
文章鏈接:http://www.dlmjj.cn/article/cdihjoe.html


咨詢
建站咨詢
