<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          await,async 我要把它翻個底朝天,這回你總該明白了吧

          共 7437字,需瀏覽 15分鐘

           ·

          2020-08-30 05:01

          一:背景

          1. 講故事

          await,async 這玩意的知識點(diǎn)已經(jīng)被人說的爛的不能再爛了,看似沒什么好說的,但我發(fā)現(xiàn)有不少文章還是從理論上講述了這兩個語法糖的用法,懂得還是懂,不懂的看似懂了過幾天又不懂了,人生如戲全靠記是不行的哈???,其實(shí)本質(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;
          }
          }

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

          2. 挖掘 await async 的IL代碼

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

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

          <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__1?類實(shí)現(xiàn)了接口?IAsyncStateMachine,定義如下:

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

          <2 style="box-sizing: border-box;"> GetResult ()

          為了方便演示,我對方法體中的 IL 代碼做一下簡化:


          .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`10> 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/'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<string> 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<string>::Start<class ConsoleApp3.Program/'d__1'>(!!0&)
          IL_0025:
          ldloc.0
          IL_0026: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'d__1'::'<>t__builder'
          IL_002b: call instance class [System.Runtime]System.Threading.Tasks.Task`10> 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)該知道這個方法就是做了?new d__1,然后從?IL_002b?處返回了一個?get_Task()?,這時候你就應(yīng)該明白,為什么主線程不會被阻塞,因?yàn)槿思曳祷氐氖?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) { }
          }

          可以看到,回寫成 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ì)觀察會發(fā)現(xiàn),所謂的 await,async 的異步化運(yùn)作都是由 AsyncTaskMethodBuilder 承載的,如異步任務(wù)的啟動,對html結(jié)果的封送,接觸底層IO,其中?Task?對應(yīng)著?AsyncTaskMethodBuilder, Task 對應(yīng)著 AsyncTaskMethodBuilder, 這也是為什么編譯器在 async 處一直提示你返回 Task 和?Task,如果不這樣的話的就找不到對應(yīng) 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;

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

          然后簡單說一下 狀態(tài)機(jī) 的走法,通過調(diào)試會發(fā)現(xiàn)這里會走 兩次 MoveNext,一次啟動,一次拿結(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ū)動程序 處理完畢后就由線程池IO線程主動發(fā)起到最后觸發(fā)代碼中的 MoveNext,最后就是到 awaiter 中獲取 task 的 result 處結(jié)束,如下圖:

          四:總結(jié)

          語法糖有簡單和復(fù)雜之分,復(fù)雜的也不要怕,學(xué)會將 IL 代碼翻譯成 C# ,或許你以前很多不明白的地方此時都會豁然開朗,不是嗎?


          瀏覽 48
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久综合p | 免费A片网址 | 网爆黑料一区二区三区四区 | 尹人在线大香蕉 | 成人一级黄片 |