溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解Await與Async

發(fā)布時間:2021-10-28 17:32:07 來源:億速云 閱讀:113 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“如何理解Await與Async ”,在日常操作中,相信很多人在如何理解Await與Async 問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何理解Await與Async ”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

一:背景

1. 講故事

await,async  這玩意的知識點已經(jīng)被人說的爛的不能再爛了,看似沒什么好說的,但我發(fā)現(xiàn)有不少文章還是從理論上講述了這兩個語法糖的用法,懂得還是懂,不懂的看似懂了過幾天又不懂了,人生如戲全靠記是不行的哈,其實本質(zhì)上來說  await, async 只是編譯器層面上的語法糖,在 IL 層面都會被打成原型的,所以在這個層面上認(rèn)識這兩個語法糖是非常有必要的。

二:從 IL 層面認(rèn)識

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;        }    }

如何理解Await與Async

上面的代碼非常簡單,可以看到異步操作沒有阻塞主線程輸出: 稍等... 正在下載 cnblogs -> html \r\n, 編譯器層面沒什么好說的  ,接下來看下在 IL 層面發(fā)生了什么?

2. 挖掘 await async 的IL代碼

還是老規(guī)矩, ilSpy 走起,如下圖:

如何理解Await與Async

可以看到,這里有一個 GetResult 方法 ,一個 Main 方法,還有一個不知道在哪里冒出來的d__1  類,接下來和大家一個一個聊。

<1 style="box-sizing: border-box;"> \d__1> 類

因為不知道從哪里冒出來的,特別引人關(guān)注,所以看看它的 IL 是咋樣的?

.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 代碼可以看到,這是自動生成的d__1 類實現(xiàn)了接口 IAsyncStateMachine,定義如下:

如何理解Await與Async

看到里面的 MoveNext 是不是很眼熟,平時你在 foreach 集合的時候就會用到這個方法,那時人家叫做枚舉類,在這里算是被改造了一下,  叫狀態(tài)機???。

<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.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 你就應(yīng)該知道這個方法就是做了 newd__1,然后從 IL_002b  處返回了一個 get_Task() ,這時候你就應(yīng)該明白,為什么主線程不會被阻塞,因為人家返回的是 Task,對吧,最后的 http  結(jié)果會藏在 Task中,這樣是不是就很好理解了。

<3 style="box-sizing: border-box;"> Main

Main方法沒有做任何改變,原來是什么樣現(xiàn)在還是什么樣。

三:將 IL 代碼 回寫為 C#

1. 完整 C# 代碼

通過前面一部分你應(yīng)該對 await ,async 在 IL 層面有了一個框架性的認(rèn)識,這里我就全部反寫成 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) { }     }

如何理解Await與Async

可以看到,回寫成 C# 代碼之后跑起來是沒有任何問題的,為了方便理解,我先來畫一張流程圖。

如何理解Await與Async

通過上面的 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

其實你仔細(xì)觀察會發(fā)現(xiàn),所謂的 await,async 的異步化運作都是由 AsyncTaskMethodBuilder  承載的,如異步任務(wù)的啟動,對html結(jié)果的封送,接觸底層IO,其中 Task對應(yīng)著  AsyncTaskMethodBuilder, Task 對應(yīng)著 AsyncTaskMethodBuilder, 這也是為什么編譯器在  async 處一直提示你返回 Task 和 Task,如果不這樣的話的就找不到對應(yīng) AsyncTaskMethodBuilder  了,對吧,如下圖:

如何理解Await與Async

然后著重看下 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;

一旦調(diào)用了這個方法,就需要等待 底層IO 將任務(wù)處理完畢之后二次回調(diào) GetResult.MoveNext,也就表示要么異常要么完成任務(wù), Awaiter  包裝的 Task 結(jié)果封送到 builder.SetResult。

然后簡單說一下 狀態(tài)機 的走法,通過調(diào)試會發(fā)現(xiàn)這里會走 兩次 MoveNext,一次啟動,一次拿結(jié)果。

<1> 第一次回調(diào) MoveNext

第一次 MoveNext 的觸發(fā)由 stateMachine.builder.Start(ref stateMachine) 發(fā)起,可以用 dnspy  去調(diào)試一下,如下圖:

如何理解Await與Async

<2> 第二次回調(diào) MoveNext

第二次 MoveNext 的觸發(fā)由 builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref  stateMachine) 開始,可以看到一旦 網(wǎng)絡(luò)驅(qū)動程序 處理完畢后就由線程池IO線程主動發(fā)起到最后觸發(fā)代碼中的 MoveNext,最后就是到  awaiter 中獲取 task 的 result 處結(jié)束,如下圖:

如何理解Await與Async

到此,關(guān)于“如何理解Await與Async ”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI