async/await 貼臉輸出,這次你總該明白了

出來混總是要還的

最近在準備記錄一個.NET Go核心能力的深度對比, 關於.NET/Go的異步實現總感覺沒敲到點上。

async/await是.NET界老生常談的話題,每至於此,狀態機又是必聊的話題,但是狀態機又是比較晦澀難懂的話題。

[一線碼農大佬]在博客園2020年寫的《await,async 我要把它翻個底朝天,這回你總該明白了吧》手把手實現了異步狀態機,這篇文章很是經典, 但是評論區很多人還是在吐槽看不懂, 我也看的不是很懂。

以我淺薄的推測:

  1. 一線大佬的知識體系太寬太深,有的驗證點在文字之外,需要我們自己去確認。
  2. 有些內容太細節,挖的太深,出不來。
  3. 很多人不熟悉狀態機設計模式, 導致看大佬文章,知其然不知其所以然。

我以前用Go語言演示了狀態機: 我是狀態機,有一顆永遠騷動的機器引擎, 當時有粉絲留言讓用.NET 實現狀態機, 這篇文章也算是對粉絲的喊話。

狀態機:一顆永遠騷動的機器引擎

狀態機是一種行爲設計模式,它允許對象在其內部狀態改變時改變其行爲。看起來好像對象改變了它的類。

請仔細理解上面每一個字。

我們以自動售貨機爲例,爲簡化演示,我們假設自動售貨機只有1種商品, 故自動售貨機有itemCountitemPrice 2個屬性

不考慮動作的前後相關性,自動售貨機對外暴露4種行爲:

  • 給自動售貨機加貨 addItem
  • 選擇商品 requestItem
  • 付錢 insertMoney
  • 出貨 dispenseItem

重點來了,當發生某種行爲,自動售貨機會進入如下4種狀態之一, 並據此狀態做出特定動作, 之後進入另外一種狀態.....

  • 有商品 hasItem
  • 無商品 noItem
  • 已經選好商品 itemRequested
  • 已付錢 hasMoney

當對象可能處於多種不同的狀態之一、根據傳入的動作更改當前的狀態, 繼續接受後續動作,狀態再次發生變化.....

這樣的模式類比於機器引擎,週而復始的工作和狀態轉化,這也是狀態機的定語叫“機Machine”的原因。

有了以上思路,我們嘗試溝通UML 僞代碼

狀態機設計模式的僞代碼實現:

  • 所謂的機器Machine維護了狀態切換的上下文
  • 機器對外暴露的行爲,驅動機器的狀態變更
  • 機器到達特定的狀態 只具備特定的行爲,其他行爲是不被允許的

Go版本的售貨機(狀態機設計模式)的源碼,請參見原文https://www.cnblogs.com/JulianHuang/p/15304184.html。

async/await貼臉開大

還是以一線碼農大佬的異步下載爲例:

編譯器詞法分析定位到async/await語法糖,就會爲開發者生成狀態機代碼。

一個新出爐的狀態機包含如下屬性 :


(1) 初始化的狀態機,以async所在的函數名命名,示例狀態機爲<GetResult>d__1

(2)車鑰匙啓動狀態機之後,立馬返回,這正是異步編程的內涵。

一個簡單的、成功的狀態機轉化如圖:

1. 初始狀態

  • state= -1;
  • Start狀態機; 即時返回。
Program.<GetResult>d__1 stateMachine = new Program.<GetResult>d__1();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start<Program.<GetResult>d__1>(ref stateMachine);
return stateMachine.<>t__builder.Task;

車鑰匙Start,內部實際是執行MoveNext方法
該方法會設置異步任務的TaskAwaiter對象, 緊接着stateMachine進入新的狀態。

2. 異步任務未完成狀態

int num1 = this.<>1__state;

if (num1 != 0)
{
    this.<client>5__1 = new WebClient();
    awaiter = this.<client>5__1.DownloadStringTaskAsync(new Uri("http://cnblogs.com")).GetAwaiter();
   if (!awaiter.IsCompleted)
  {
      this.<>1__state = num2 = 0;
      this.<>u__1 = awaiter;
      Program.<GetResult>d__1 stateMachine = this;
     this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Program.<GetResult>d__1>(ref awaiter, ref stateMachine);
      return;
  }
}

IO數據就緒,會在IO線程執行回調方法GetCompletionAction,利用入參2:狀態機,再次執行狀態機的MoveNext方法, 進入新的狀態

3. 異步結果就緒狀態

  • 切換到state = -1;
  • taskAwaiter獲取異步任務結果;
  • 執行後繼代碼;
else
{
    awaiter = this.<>u__1;
    this.<>u__1 = new TaskAwaiter<string>();
    this.<>1__state = num2 = -1;
}
this.<>s__3 = awaiter.GetResult();
this.<content>5__2 = this.<>s__3;
this.<>s__3 = (string) null;
content52 = this.<content>5__2;   // 後繼代碼段

4. 狀態機終止狀態

  • 切換到state =-2;
  • 設置狀態機最終返回值;
this.<>1__state = -2;
this.<client>5__1 = (WebClient) null;
this.<content>5__2 = (string) null;
this.<>t__builder.SetResult(content52);

以上四個狀態的貼臉源碼均截取自ILspy反編譯結果,讀者可將代碼和狀態輪轉圖對比。


一線碼農大佬講: 一個簡單成功的async/await狀態機會經歷 2次MoveNext動作 ,我是認同的。

一次是狀態機啓動,主動切換狀態;

第二次是IO數據就緒,回調函數會執行原狀態機的MoveNext方法, 這個是在註冊回調的時候確定的。

下面是第二次MoveNext方法的執行堆棧(包含github地址):

結束語

本文重點從狀態機設計模式的角度,演示了async/await語法糖的內部實現。

通過一個騷動的機器引擎,演示了開啓異步任務---> 異步任務完成---> 設置狀態機輸出結果的全過程,而這4個狀態的變遷又催生了.NET異步編程的帶來的性能優勢。

最後:本文是一線碼農大佬(博客園12349粉絲博主)《異步async/await底朝天》的狗尾續貂,respect !!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章