進階篇:以IL爲劍,直指async/await

接上篇:30分鐘?不須要,輕鬆讀懂IL,這篇主要從IL入手來理解async/await的工做原理。 html

先簡單介紹下async/await,這是.net 4.5引入的語法糖,配合Task使用能夠很是優雅的寫異步操做代碼,它自己並不會去建立一個新線程,線程的工做仍是由Task來作,async/await只是讓開發人員以直觀的方式寫異步操做代碼,而不像之前那樣處處都是callback或事件。 git

async/await IL翻譯

先寫個簡單的例子:github

 1 using System;
 2 using System.Threading.Tasks;
 3 
 4 namespace ILLearn
 5 {
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             DisplayDataAsync();
11 
12             Console.ReadLine();
13         }
14 
15         static async void DisplayDataAsync()
16         {
17             Console.WriteLine("start");
18 
19             var data = await GetData();
20 
21             Console.WriteLine(data);
22 
23             Console.WriteLine("end");
24         }
25 
26         static async Task<string> GetData()
27         {
28             await Task.Run(async () => await Task.Delay(1000));
29             return "data";
30         }
31     }
32 }

編譯: csc /debug- /optimize+ /out:program.exe program.cs 生成program.exe文件,用ildasm.exe打開,以下:編程

發現多出來兩個結構,帶<>符號的通常都是編譯時生成的:<DisplayDataAsync>d_1和<GetData>d_2,安全

<DisplayDataAsync>d_1是咱們此次的目標,來分析一下:異步

這個結構是給DisplayDataAsync用的,名字很差,實現了IAsyncStateMachine接口,看名字知道一個狀態機接口,原來是編譯時生成了一個狀態機,有3個字段,2個接口函數,咱們整理一下狀態機代碼: async

 1 struct GetDataAsyncStateMachine : IAsyncStateMachine
 2 {
 3     public int State;
 4 
 5     public AsyncVoidMethodBuilder Builder;
 6 
 7     private TaskAwaiter<string> _taskAwaiter;
 8 
 9     void IAsyncStateMachine.MoveNext();
10 
11     void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine);
12 }

這樣就好看多了。 ide

再來看看咱們寫的DisplayDataAsync的IL: 函數

雙擊post

 1 .method private hidebysig static void  DisplayDataAsync() cil managed
 2 {
 3   .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 26 49 4C 4C 65 61 72 6E 2E 50 72 6F 67 72   // ..&ILLearn.Progr
 4                                                                                                                                      61 6D 2B 3C 44 69 73 70 6C 61 79 44 61 74 61 41   // am+<DisplayDataA
 5                                                                                                                                      73 79 6E 63 3E 64 5F 5F 31 00 00 )                // sync>d__1..
 6   // 代碼大小       37 (0x25)
 7   .maxstack  2
 8   .locals init (valuetype ILLearn.Program/'<DisplayDataAsync>d__1' V_0,  //這裏仍是局部變量,第1個是valuetype也就是值類型<DisplayDataAsync>d__1,在上面知道這是一個狀態機 DisplayDataAsyncStateMachine
 9            valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder V_1) //第2個局部變量也是值類型,叫AsyncVoidMethodBuilder,在System.Runtime.CompilerServices命名空間下
10   IL_0000:  ldloca.s   V_0  //加載第1個局部變量的地址,由於是結構,在棧上,經過地址來調用函數
11   IL_0002:  call       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create()  //調用AsyncVoidMethodBuilder的create函數,用的是call,而且沒有實例,因此create()是個靜態函數
12   IL_0007:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder'  //把create()的結果存到DisplayDataAsyncStateMachine結構的Builder字段
13   IL_000c:  ldloca.s   V_0  //加載第1個局部變量的地址,仍是爲了給這個結構的變量賦值
14   IL_000e:  ldc.i4.m1  //加載整數 -1,上篇沒有說,這個m表示minus,也就是負號
15   IL_000f:  stfld      int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state'  //把-1存到DisplayDataAsyncStateMachine的State字段
16   IL_0014:  ldloc.0   //加載第1個局部變量
17   IL_0015:  ldfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //獲取第1個局部變量的Builder字段,也就是上面create()出來的
18   IL_001a:  stloc.1  //存到第2個局部變量中 V_1 = DisplayDataAsyncStateMachine.Builder
19   IL_001b:  ldloca.s   V_1  //加載第1個局部變量地址
20   IL_001d:  ldloca.s   V_0  //加載第2個局部變量地址
21   IL_001f:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<valuetype ILLearn.Program/'<DisplayDataAsync>d__1'>(!!0&)  //調用V_0的start方法,方法有個參數!!0&,這看上去有點奇怪,指的是上面加載的V_1的地址
22   IL_0024:  ret //返回
23 } // end of method Program::DisplayDataAsync

好了,這個函數的意思差很少搞懂了,咱們先把它翻譯成容易看懂的C#代碼,大概是這個樣子:

 1 public void DisplayDataAsync()
 2 {
 3     DisplayDataAsyncStateMachine stateMachine;
 4 
 5     stateMachine.Builder = AsyncVoidMethodBuilder.Create();
 6 
 7     stateMachine.State = -1;
 8 
 9     AsyncVoidMethodBuilder builder = stateMachine.Builder;
10 
11     builder.Start(ref stateMachine);
12 }

與源代碼徹底不同。

GetDataAsyncStateMachine還有兩個接口函數的IL須要看下,接下來先看看這兩個函數SetStateMachine和MoveNext的IL代碼,把它也翻譯過來,注意:IL裏用的<DisplayDataAsync>d_1,<>1_state,<>_builder,<>u_1均可以用GetDataAsyncStateMachine,State, Builder,_taskAwaiter來表示了,這樣更容易理解一些。

MoveNext:

  1 .method private hidebysig newslot virtual final 
  2         instance void  MoveNext() cil managed
  3 {
  4   .override [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext
  5   // 代碼大小       175 (0xaf)
  6   .maxstack  3
  7   .locals init (int32 V_0,  
  8            valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> V_1,
  9            class [mscorlib]System.Exception V_2)  //3個局部變量
 10   IL_0000:  ldarg.0  //加載第0個參數,也就是自己
 11   IL_0001:  ldfld      int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state'  //加載字段State
 12   IL_0006:  stloc.0  //存到第1個局部變量中,也就是V_0 = State
 13   .try //try 塊
 14   {
 15     IL_0007:  ldloc.0  //加載第1個局部變量
 16     IL_0008:  brfalse.s  IL_0048  //是false也就是 V_0 == 0則跳轉到IL_0048
 17     IL_000a:  ldstr      "start"  //加載string "start"
 18     IL_000f:  call       void [mscorlib]System.Console::WriteLine(string)  //調用Console.WriteLine("start")
 19     IL_0014:  call       class [mscorlib]System.Threading.Tasks.Task`1<string> ILLearn.Program::GetData()  //調用靜態方法Program.GetData()
 20     IL_0019:  callvirt   instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<string>::GetAwaiter() //調用GetData()返回Task的GetAwaiter()方法
 21     IL_001e:  stloc.1  //把GetAwaiter()的結果存到第2個局部變量中也就是V_1 = GetData().GetAwaiter()
 22     IL_001f:  ldloca.s   V_1  //加載第2個局部變量V_1的地址
 23     IL_0021:  call       instance bool valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>::get_IsCompleted()  //調用實例屬性 IsCompleted
 24     IL_0026:  brtrue.s   IL_0064  //若是V_1.IsCompleted == true則跳轉到IL_0064
 25     IL_0028:  ldarg.0  //加載this
 26     IL_0029:  ldc.i4.0  //加載整數0
 27     IL_002a:  dup  //複製, 由於要存兩份
 28     IL_002b:  stloc.0  //存到第1個局部變量中,V_0=0
 29     IL_002c:  stfld      int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //存到State,State=0
 30     IL_0031:  ldarg.0  //加載this
 31     IL_0032:  ldloc.1  //加載第2個局部變量
 32     IL_0033:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1'  //存到<>u__1也就是_taskAwaiter中,_taskAwaiter = V_1
 33     IL_0038:  ldarg.0  //加載this
 34     IL_0039:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //加載Builder的地址
 35     IL_003e:  ldloca.s   V_1  //加載V_1的地址
 36     IL_0040:  ldarg.0  //加載this
 37     IL_0041:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>,valuetype ILLearn.Program/'<DisplayDataAsync>d__1'>(!!0&,!!1&)//調用Builder的AwaitUnsafeOnCompleted函數,第1個參數是v1的地址,第2個是this,都是引用
 38     IL_0046:  leave.s    IL_00ae  // 跳到IL_00ae,也就是return
 39     IL_0048:  ldarg.0  //從IL_0008跳過來,加載this
 40     IL_0049:  ldfld      valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1'  //加載_taskAwaiter
 41     IL_004e:  stloc.1  //存到第2個局部變量,V_1 = _taskAwaiter
 42     IL_004f:  ldarg.0  //加載this
 43     IL_0050:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1'  //加載_taskAwaiter地址
 44     IL_0055:  initobj    valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>  //初始化結構,也就是_taskAwaiter = default(TaskAwaiter<string>)
 45     IL_005b:  ldarg.0  //加載this
 46     IL_005c:  ldc.i4.m1  //加載-1
 47     IL_005d:  dup  //複製
 48     IL_005e:  stloc.0  //把-1存到V_0中,V_0 = -1
 49     IL_005f:  stfld      int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state'  //存到State,State=-1
 50     IL_0064:  ldloca.s   V_1  //從IL_0026跳過來的,加載V_1的地址
 51     IL_0066:  call       instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>::GetResult()  //調用V_1.GetResult()
 52     IL_006b:  ldloca.s   V_1 //加載V_1的地址
 53     IL_006d:  initobj    valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>  //初始化結構,也就是V_1 = default(TaskAwaiter<string>)
 54     IL_0073:  call       void [mscorlib]System.Console::WriteLine(string)  // Console.WriteLine 寫GetResult返回的值
 55     IL_0078:  ldstr      "end"
 56     IL_007d:  call       void [mscorlib]System.Console::WriteLine(string)  //Console.WriteLine("end")
 57     IL_0082:  leave.s    IL_009b  //沒異常,跳到IL_009b
 58   }  // end .try
 59   catch [mscorlib]System.Exception  //catch 塊
 60   {
 61     IL_0084:  stloc.2  //把異常存到V_2
 62     IL_0085:  ldarg.0  //加載this
 63     IL_0086:  ldc.i4.s   -2  //加載-2
 64     IL_0088:  stfld      int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state'  //State = -2
 65     IL_008d:  ldarg.0  //加載this
 66     IL_008e:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder'  //加載Builder的地址
 67     IL_0093:  ldloc.2  //加載第3個局部變量Exception
 68     IL_0094:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetException(class [mscorlib]System.Exception)  //調用Builder.SetException,參數就是第3個局部變量
 69     IL_0099:  leave.s    IL_00ae  //return
 70   }  // end handler
 71   IL_009b:  ldarg.0  //加載this
 72   IL_009c:  ldc.i4.s   -2 //加載-2
 73   IL_009e:  stfld      int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state'  //State = -2
 74   IL_00a3:  ldarg.0 //加載this
 75   IL_00a4:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder'//加載Builder的地址
 76   IL_00a9:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetResult()  //Builder.SetResult()
 77   IL_00ae:  ret  //return
 78 } // end of method '<DisplayDataAsync>d__1'::MoveNext
 79 
 80 翻譯整理一下:  81 V_0用state表示, V_1用awaiter表示,V_2用ex表示
 82 
 83 void IAsyncStateMachine.MoveNext()
 84 {
 85     int state = State;
 86     try
 87     {
 88         TaskAwaiter<string> awaiter;
 89         if (state != 0)  // 狀態不是0就進來,默認是-1
 90         {
 91             Console.WriteLine("start");  //  執行 await 以前的部分
 92 
 93             awaiter = Program.GetData().GetAwaiter();  // 獲取 awaiter
 94 
 95             if (!awaiter.IsCompleted)  //判斷是否完成,完成的話就不用分開了,直接執行後面的
 96             {
 97                 state = 0;
 98                 State = 0;  // 把狀態變爲0, awaiter執行完成後就不用進這裏了
 99                 _taskAwaiter = awaiter;  // 保存awaiter, awaiter回來後要靠_taskAwaiter來取結果
100                 Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);  // 這裏面主要是構造一個action - MoveNextRunner,用來在awaiter.complete事件觸發後走到這個狀態機的MoveNext(),上面把state變了0了,再走這個函數的話就能夠走到await後面的部分,後面再詳細講
101                 return;  // 返回
102             }
103         }
104         else
105         {
106             awaiter = _taskAwaiter;
107             state = -1;
108             State = -1;
109         }
110 
111         var result = awaiter.GetResult(); //awaiter回來後取得結果
112 
113         Console.WriteLine(result);  // 走 await 後面的部分
114 
115         Console.WriteLine("end");
116     }
117     catch(Exception ex)
118     {
119         State = -2;
120         Builder.SetException(ex);
121     }
122     
123     State = -2;
124     Builder.SetResult();
125 }

SetStateMachine:

 1 .method private hidebysig newslot virtual final 
 2         instance void  SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) cil managed
 3 {
 4   .custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 ) 
 5   .override [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine
 6   // 代碼大小       13 (0xd)
 7   .maxstack  8
 8   IL_0000:  ldarg.0
 9   IL_0001:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder'
10   IL_0006:  ldarg.1
11   IL_0007:  call       instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
12   IL_000c:  ret
13 } // end of method '<DisplayDataAsync>d__1'::SetStateMachine
14 
15 這個很簡單,就不一一寫了,直接翻譯: 16 void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
17 {
18     Builder.SetStateMachine(stateMachine);
19 }

由於是照着IL直譯,代碼可能有點冗餘,不過不傷大雅。

async/await原理

如今疏理一下,從DisplayDataAsync開始,先是建立一個狀態機,把狀態變量State初始化爲-1,Builder使用AsyncVoidMethodBuilder.Create來建立,既而調用這個builder的Start函數並把狀態機的引用傳過去。

那重點就是這個AsyncVoidMethodBuilder的做用,AsyncVoidMethodBuilder在命名空間System.Runtime.CompilerServices下,咱們來讀一下它的源碼,.net的BCL已經開源了,因此直接去github上找就好了。

https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs

這文件裏面有這麼幾個重要類AsyncVoidMethodBuilder,AsyncTaskMethodBuilder,AsyncTaskMethodBuilder<T>,AsyncMethodBuilderCore及AsyncMethodBuilderCore內的MoveNextRunner。

首先爲何DsiplayDataAsync用到的是AsyncVoidMethodBuilder,由於DisplayDataAsync返回的是void,在ildasm裏雙擊GetData你會發現以下IL:

1 IL_0002: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Create()

GetData用的是AsyncTaskMethodBuilder<string>,由於GetData返回的是Task<string>。那咱們就知道了,AsyncVoidMethodBuilder,AsyncTaskMethodBuilder,AsyncTaskMethodBuilder<T>這三個類分別對應返回爲void, Task和Task<T>的異步函數,由於async標記的函數只能返回這三種類型。這三個類的功能差很少,代碼大同小異,咱們就拿用到的AsyncVoidMethodBuilder來講。

先看最早調用的Create()函數:

1 public static AsyncVoidMethodBuilder Create()
2 {
3     SynchronizationContext sc = SynchronizationContext.CurrentNoFlow;
4     if (sc != null)
5         sc.OperationStarted();
6     return new AsyncVoidMethodBuilder() { m_synchronizationContext = sc };
7 }

SynchronizationContext.CurrentNoFlow做用是取得當前線程的SynchronizationContext,這個有什麼用呢,SynchronizationContext能夠算是一個抽象概念的類(這個類自己不是抽象的),它提供了線程間通信的橋樑,通常線程的SynchronizationContext.Current爲空,但主線程除外,好比對於WinForm,在第一個窗體建立時,系統會給主線程添加SynchronizationContext,也就是SynchronizationContext.Current = new WinFormSynchronizationContext(),WinFormSynchronizationContext是繼承SynchronizationContext並從新實現了一些方法如Send,Post,Send, Post都是經過Control.Invoke/BeginInvoke來實現與UI線程的通信。

對應的WPF的就是DispatcherSynchronizationContext,Asp.net就是AspNetSynchronizationContext。

固然,這裏的SynchronizationContext是用來作跨線程Exception處理的,Task的Exception爲何能在外面捕獲到,就靠這個SynchronizationContext,這個後面詳細再講。

好了,Create函數看完,接下來看Start()函數。

 1 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
 2 {
 3     if (stateMachine == null) throw new ArgumentNullException("stateMachine");
 4     Contract.EndContractBlock();
 5 
 6     ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
 7     RuntimeHelpers.PrepareConstrainedRegions();
 8     try
 9     {
10         ExecutionContext.EstablishCopyOnWriteScope(ref ecs);
11  stateMachine.MoveNext(); 12     }
13     finally
14     {
15         ecs.Undo();
16     }
17 }
Contract.EndContractBlock();這個是一個契約標記,通常用在throw後面,沒功能性的做用,這裏很少講,有興趣的能夠去翻下契約式編程。

先看看ExecutionContext

https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Threading/ExecutionContext.cs

ExecutionContext能夠認爲是一個容器,裏面包含了一組context,SynchronizationContext是裏面其中一個,還有如SecretContext,LogicContext等,表明了線程所執行的上下文。

ExecutionContextSwitcher這個類型又是幹什麼的呢,看代碼:

 1 internal struct ExecutionContextSwitcher
 2 {
 3     internal ExecutionContext m_ec;
 4     internal SynchronizationContext m_sc;
 5 
 6     internal void Undo()
 7     {
 8         SynchronizationContext.SetSynchronizationContext(m_sc);
 9         ExecutionContext.Restore(m_ec);
10     }
11 }

也是一個結構,主要用來作Undo操做的,也就是在執行MoveNext時若是出現異常,能夠恢復原來的上下文。

接着看Start函數,RuntimeHelpers.PrepareConstrainedRegions() 就是CER(Constrained Execution Region),通常由RuntimeHelpers.PrepareConstrainedRegions() + try..catch..finally組成,用來告訴CLR這段代碼很重要,不論是什麼異常都不要打斷,爲了保證不被打斷, CER內(catch和finally塊)的代碼不能在堆上有操做,而且預先編譯好CER內的代碼,一切都是爲了防止被打斷。

說到預編譯,CLR裏還有個操做也是要預編譯的,就是派生自CriticalFinalizerObjectFinalizer的類,這些類會確保它們的Finalize會被執行。

GC若是是由於內存不足而觸發,而這時Finalize若是沒有預編譯,就有可能發生沒有內存可供Finalize編譯,Finalize得不到執行,對象也不能被釋放,從而形成資源泄漏。

進入try塊,執行ExecutionContext.EstblishCopyOnWriteScope(ref ecs)這個函數,接着看它的代碼:

1 static internal void EstablishCopyOnWriteScope(ref ExecutionContextSwitcher ecsw)
2 {
3     ecsw.m_ec = Capture();
4     ecsw.m_sc = SynchronizationContext.CurrentNoFlow;
5 }

原來是給ExecutionContextSwitcher的屬性賦值,Capture函數是抓取當前線程的ExecutionContext,這樣ExecutionContextSwitcher裏的Context就能夠保存下來以便異常時恢復了。

繼續Start函數,最重要的stateMachine.MoveNext()來了,上面一大堆都是爲了這個傢伙的安全執行。

整個Start看完,目的也就是執行MoveNext,那咱們看看狀態機裏MoveNext幹了些什麼:

看看咱們上面翻譯的結果:

 1 void IAsyncStateMachine.MoveNext()
 2 {
 3     int state = State;
 4 
 5     try
 6     {
 7         TaskAwaiter<string> awaiter;
 8 
 9         if (state != 0) // 狀態不是0就進來,默認是-1
10         {
11             Console.WriteLine("start"); // 執行 await 以前的部分
12             awaiter = Program.GetData().GetAwaiter(); // 獲取 awaiter
13 
14             if (!awaiter.IsCompleted) //判斷是否完成,完成的話就不用分開了,直接執行後面的
15             {
16                 state = 0;
17                 State = 0; // 把狀態變爲0, awaiter執行完成後再次MoveNext就不用進這裏了
18                 _taskAwaiter = awaiter; // 保存awaiter, awaiter回來後要靠_taskAwaiter來取結果
19                 Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); // 這裏面主要是構造一個action - MoveNextRunner,用來在awaiter.complete事件觸發後繼續走這個狀態機的MoveNext(),上面把state變了0了,再走這個函數的話就能夠走到await後面的部分,下面再詳細講
20 
21                 return; // 返回
22             }
23         }
24         else
25         {
26             awaiter = _taskAwaiter;
27             state = -1;
28             State = -1;
29         }
30 
31         var result = awaiter.GetResult(); //awaiter回來後取得結果
32         Console.WriteLine(result); // 走 await 後面的部分
33         Console.WriteLine("end");
34     }
35     catch (Exception ex)
36     {
37         State = -2;
38         Builder.SetException(ex);
39     }
40 
41     State = -2;
42     Builder.SetResult();
43 }

能夠把原始代碼當作三段,如圖:

第一次進來因爲state是-1,因此先執行第一段,接着是第二段,把state置爲0而且拿到awaiter作Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this)操做,這個操做裏面會在取到數據後再次MoveNext,由於state爲0,因此就走到第三段,整個過程是這樣。

咱們詳細看看Builder.AwaitUnsafeOnCompleted這個操做是怎麼調用第二次MoveNext的。

 1 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
 2     ref TAwaiter awaiter, ref TStateMachine stateMachine)
 3     where TAwaiter : INotifyCompletion
 4     where TStateMachine : IAsyncStateMachine
 5 {
 6     try
 7     {
 8         AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
 9         var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
10         Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
11 
12         // If this is our first await, such that we've not yet boxed the state machine, do so now.
13         if (m_coreState.m_stateMachine == null)
14         {
15             if (AsyncCausalityTracer.LoggingOn)
16                 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0);
17                     
18             m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null);
19         }
20 
21  awaiter.OnCompleted(continuation);
22     }
23     catch (Exception exc)
24     {
25         AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null);
26     }
27 }

一點一點看,先調用了m_coreState.GetCompletionAction,m_coreState是AsyncMethodBuilderCore類型,來看看它的實現:

 1 internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize)
 2 {
 3     Contract.Assert(m_defaultContextAction == null || m_stateMachine != null,
 4         "Expected non-null m_stateMachine on non-null m_defaultContextAction");
 5 
 6     Debugger.NotifyOfCrossThreadDependency();
 7 
 8     var capturedContext = ExecutionContext.FastCapture();  //獲取當前線程的ExecutionContext
 9     Action action;
10     MoveNextRunner runner;
11     if (capturedContext != null && capturedContext.IsPreAllocatedDefault)
12     {
13         action = m_defaultContextAction;
14         if (action != null)
15         {
16             Contract.Assert(m_stateMachine != null, "If the delegate was set, the state machine should have been as well.");
17             return action;
18         }
19         runner = new MoveNextRunner(capturedContext, m_stateMachine);  //new一個MoveNextRunner實例,並把ExecutionContext和狀態機傳過去
20 
21         action = new Action(runner.Run);  //runner.Run的action
22         if (taskForTracing != null)
23         {
24             m_defaultContextAction = action = OutputAsyncCausalityEvents(taskForTracing, action);
25         }
26         else
27         {
28             m_defaultContextAction = action;
29         }
30     }
31     else
32     {
33         runner = new MoveNextRunner(capturedContext, m_stateMachine); 34         action = new Action(runner.Run); 35 
36         if (taskForTracing != null)
37         {
38             action = OutputAsyncCausalityEvents(taskForTracing, action);
39         }
40     }
41 
42     if (m_stateMachine == null)
43         runnerToInitialize = runner;
44 
45     return action; 46 }

這段代碼看起來比較簡單,主要是針對MoveNextRunner實例,傳遞上下文和狀態機給它,你們應該能夠猜到MoveNext就是用這個MoveNextRunner.Run去實現了,這個函數返回的就是MoveNextRunner.Run。

再回頭看上面的代碼,若是m_coreState.m_stateMachine == null,也就是第一次進來就先作PostBoxInitialization操做,看看PostBoxInitialization:

 1 internal void PostBoxInitialization(IAsyncStateMachine stateMachine, MoveNextRunner runner, Task builtTask)
 2 {
 3     if (builtTask != null)
 4     {
 5         if (AsyncCausalityTracer.LoggingOn)
 6             AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, builtTask.Id, "Async: " + stateMachine.GetType().Name, 0);
 7 
 8         if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
 9             System.Threading.Tasks.Task.AddToActiveTasks(builtTask);
10     }
11 
12     m_stateMachine = stateMachine;  //給m_stateMachine賦值,由於m_stateMachine是internal IAsyncStateMachine m_stateMachine;這樣定義的,因此把struct stateMachine傳給這個接口類型時會裝箱,目的是在Builder裏面保存這個狀態機,下次不會走這了
13  m_stateMachine.SetStateMachine(m_stateMachine); 14 
15     Contract.Assert(runner.m_stateMachine == null, "The runner's state machine should not yet have been populated.");
16     Contract.Assert(m_stateMachine != null, "The builder's state machine field should have been initialized.");
17 
18     runner.m_stateMachine = m_stateMachine; 19 }

這個函數的目的有兩個,一個是給狀態機裝箱保存下來,另外一個是給runner的狀態機賦值。

再看回上面的AwaitUnsafeOnCompleted函數,到awaiter.UnsafeOnCompleted(continuation)了,這個算是核心,主要就是等這個回來再調用continuation,continuation咱們知道是MoveNextRunner的Run函數,先看看這個Run函數:

 1 internal void Run()
 2 {
 3     Contract.Assert(m_stateMachine != null, "The state machine must have been set before calling Run.");
 4 
 5     if (m_context != null)
 6     {
 7         try
 8         {
 9             ContextCallback callback = s_invokeMoveNext; 10             if (callback == null) { s_invokeMoveNext = callback = InvokeMoveNext; } 11 
12             ExecutionContext.Run(m_context, callback, m_stateMachine, preserveSyncCtx: true);  //主要就是用ExecutionContext應用到當前線程來執行這個((IAsyncStateMachine)stateMachine).MoveNext()
13         }
14         finally { m_context.Dispose(); }
15     }
16     else
17     {
18         m_stateMachine.MoveNext();
19     }
20 }
21 
22 private static ContextCallback s_invokeMoveNext;
23 
24 private static void InvokeMoveNext(object stateMachine)
25 {
26  ((IAsyncStateMachine)stateMachine).MoveNext(); 27 }

Run的目的很簡單,m_context是await以前的線程上下文,因此就是以執行Console.WriteLine("start")同樣的線程上下文去執行MoveNext,用這個ExecutionContext.Run並非說Console.WriteLine("start")和Console.WriteLine("end")會在同一個線程,ExecutionContext.Run只是在線程池裏拿一個空閒的線程,賦予一樣的上下文來執行MoveNext()。

如今只有awaiter.UnsafeOnCompleted(continuation)還沒講,不過功能已經清楚,就是awaiter completed後回調continuation,追根到底看看它是怎麼實現的:

https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs
1 public void UnsafeOnCompleted(Action continuation)
2 {
3     OnCompletedInternal(m_task, continuation, continueOnCapturedContext: true, flowExecutionContext: false);
4 }

continueOnCapturedContext這個是由Task.ConfigureAwait(continueOnCapturedContext)來控制的,true則表示執行完task後轉到SynchronizationContext所在的線程上去執行await後面的部分,好比說更新UI就必須在UI線程上,這個就須要設爲true,若是不是要更新UI,而是還有不少的數據須要本地計算,則最好設爲false,這時會在task執行完成後在線程池中拿出一個空閒的工做線程來作await後面的事,固然在Asp.net裏要注意HttpContext.Current可能在false時會爲Null,操做時須要注意。接着看OnCompletedInternal的代碼:

 1 internal static void OnCompletedInternal(Task task, Action continuation, bool continueOnCapturedContext, bool flowExecutionContext)
 2 {
 3     if (continuation == null) throw new ArgumentNullException("continuation");
 4     StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
 5 
 6     if (TplEtwProvider.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
 7     {
 8         continuation = OutputWaitEtwEvents(task, continuation);
 9     }
10 
11     task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext, ref stackMark); 12 }

主要是調用SetContinuationForAwait:

 1 internal void SetContinuationForAwait(
 2             Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark)
 3 {
 4     Contract.Requires(continuationAction != null);
 5 
 6 
 7     TaskContinuation tc = null;
 8 
 9     if (continueOnCapturedContext)  //若是須要用到SynchronizationContext
10     {
11         var syncCtx = SynchronizationContext.CurrentNoFlow;  //獲取當前SynchronizationContext
12         if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))  //當前SynchronizationContext和傳進來的SynchronizationContext不相等
13         {
14             tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction, flowExecutionContext, ref stackMark);  //用SynchronizationContext來轉到目標線程去執行
15         }
16         Else
17                 {
18             var scheduler = TaskScheduler.InternalCurrent;
19             if (scheduler != null && scheduler != TaskScheduler.Default)
20             {
21                 tc = new TaskSchedulerAwaitTaskContinuation(scheduler, continuationAction, flowExecutionContext, ref stackMark);
22             }
23         }
24     }
25 
26     if (tc == null && flowExecutionContext)
27     {
28         tc = new AwaitTaskContinuation(continuationAction, flowExecutionContext: true, stackMark: ref stackMark); // continueOnCapturedContext = false時
29     }
30 
31     if (tc != null)
32     {
33         if (!AddTaskContinuation(tc, addBeforeOthers: false))
34             tc.Run(this, bCanInlineContinuationTask: false);  //開始執行Run
35     }
36     else
37     {
38         Contract.Assert(!flowExecutionContext, "We already determined we're not required to flow context.");
39         if (!AddTaskContinuation(continuationAction, addBeforeOthers: false))
40             AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
41     }
42 }

最主要看是怎麼Run的,先看第一種,continueOnCapturedContext爲true的:

 1 internal sealed override void Run(Task task, bool canInlineContinuationTask)
 2 {
 3     if (canInlineContinuationTask && this.m_syncContext == SynchronizationContext.CurrentNoFlow)  //若是當前線程的SynchronizationContext和syncContext同樣,那表示就是一個線程,直接執行就行了
 4     {
 5         base.RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), this.m_action, ref Task.t_currentTask);
 6         return;
 7     }
 8     TplEtwProvider log = TplEtwProvider.Log;
 9     if (log.IsEnabled())
10     {
11         this.m_continuationId = Task.NewId();
12         log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, this.m_continuationId);
13     }
14     base.RunCallback(SynchronizationContextAwaitTaskContinuation.GetPostActionCallback(), this, ref Task.t_currentTask);  // 這裏用到了GetPostActionCallback()來執行
15 }

看看PostAction:

 1 private static void PostAction(object state)
 2 {
 3     SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = (SynchronizationContextAwaitTaskContinuation)state;
 4     if (TplEtwProvider.Log.TasksSetActivityIds && synchronizationContextAwaitTaskContinuation.m_continuationId != 0)
 5     {
 6         synchronizationContextAwaitTaskContinuation.m_syncContext.Post(SynchronizationContextAwaitTaskContinuation.s_postCallback, SynchronizationContextAwaitTaskContinuation.GetActionLogDelegate(synchronizationContextAwaitTaskContinuation.m_continuationId, synchronizationContextAwaitTaskContinuation.m_action));  //看到了吧,用的是SynchronizationContext的Post來執行await後面的,若是SynchronizationContext是UI線程上的,那在Winform裏就是control.BeginInvoke,在WPF裏就是Dispatcher.BeginInvoke,轉到UI線程執行
 7         return;
 8     }
 9     synchronizationContextAwaitTaskContinuation.m_syncContext.Post(SynchronizationContextAwaitTaskContinuation.s_postCallback, synchronizationContextAwaitTaskContinuation.m_action);
10 }

來看看第二種:continueOnCapturedContext爲false:

 1 internal override void Run(Task task, bool canInlineContinuationTask)
 2 {
 3     if (canInlineContinuationTask && AwaitTaskContinuation.IsValidLocationForInlining)
 4     {
 5         this.RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), this.m_action, ref Task.t_currentTask);  //這裏去到RunCallback
 6         return;
 7     }
 8     TplEtwProvider log = TplEtwProvider.Log;
 9     if (log.IsEnabled())
10     {
11         this.m_continuationId = Task.NewId();
12         log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, this.m_continuationId);
13     }
14     ThreadPool.UnsafeQueueCustomWorkItem(this, false); // 這也是經過線程池去運行
15 }
16 
17 protected void RunCallback(ContextCallback callback, object state, ref Task currentTask)
18 {
19     Task task = currentTask;
20     try
21     {
22         if (task != null)
23         {
24             currentTask = null;
25         }
26         if (this.m_capturedContext == null)
27         {
28             callback(state);
29         }
30         else
31         {
32             ExecutionContext.Run(this.m_capturedContext, callback, state, true); //就是經過ExecutionContext.Run去運行
33         }
34     }
35     catch (Exception arg_2A_0)
36     {
37         AwaitTaskContinuation.ThrowAsyncIfNecessary(arg_2A_0);
38     }
39     finally
40     {
41         if (task != null)
42         {
43             currentTask = task;
44         }
45         if (this.m_capturedContext != null)
46         {
47             this.m_capturedContext.Dispose();
48         }
49     }
50 }

因此爲false時就沒SynchronizationContext什麼事,線程池裏拿個空閒線程出來運行就行了。上面有很大篇幅講了awaiter.AwaitUnsafeOnCompleted的運行原理,由於async/await是配合awaitable用的,因此就一塊兒分析。

那如今這個簡單的async/await例子就分析完了,可能有人會以爲狀態機貌似沒什麼用,用if/else也能輕鬆作到這個,不必用MoveNext。那是由於這裏只有一個await,若是更多呢,if/else就很難控制,MoveNext就只須要關注狀態變化就行了。寫個有三個await的函數來看看:

 1 static async void DisplayDataAsync()
 2 {
 3     Console.WriteLine("start");
 4 
 5     Console.WriteLine("progress_1");
 6     await GetData();
 7 
 8     Console.WriteLine("progress_2");
 9     await GetData();
10 
11     Console.WriteLine("progress_3");
12     await GetData();
13 
14     Console.WriteLine("end");
15 }

由於IL上面已經講過,多個await的指令其實差很少,因此用另外一種簡單的方法:ILSpy來直接看翻譯結果,須要在Options裏把Decompile async method(async/await)關掉,如圖:

MoveNext的代碼:

 1 void IAsyncStateMachine.MoveNext()
 2 {
 3     int num = this.<> 1__state;
 4     try
 5     {
 6         TaskAwaiter<string> taskAwaiter;
 7         switch (num)
 8         {
 9             case 0:
10                 taskAwaiter = this.<> u__1;
11                 this.<> u__1 = default(TaskAwaiter<string>);
12                 this.<> 1__state = -1;
13                 break;
14             case 1:
15                 taskAwaiter = this.<> u__1;
16                 this.<> u__1 = default(TaskAwaiter<string>);
17                 this.<> 1__state = -1;
18                 goto IL_ED;
19             case 2:
20                 taskAwaiter = this.<> u__1;
21                 this.<> u__1 = default(TaskAwaiter<string>);
22                 this.<> 1__state = -1;
23                 goto IL_157;
24             default:
25                 Console.WriteLine("start");
26                 Console.WriteLine("progress_1");
27                 taskAwaiter = Program.GetData().GetAwaiter();
28                 if (!taskAwaiter.IsCompleted)
29                 {
30                     this.<> 1__state = 0;
31                     this.<> u__1 = taskAwaiter;
32                     this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this);
33                     return;
34                 }
35                 break;
36         }
37         taskAwaiter.GetResult();
38         taskAwaiter = default(TaskAwaiter<string>);
39         Console.WriteLine("progress_2");
40         taskAwaiter = Program.GetData().GetAwaiter();
41         if (!taskAwaiter.IsCompleted)
42         {
43             this.<> 1__state = 1;
44             this.<> u__1 = taskAwaiter;
45             this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this);
46             return;
47         }
48  IL_ED: 49         taskAwaiter.GetResult();
50         taskAwaiter = default(TaskAwaiter<string>);
51         Console.WriteLine("progress_3");
52         taskAwaiter = Program.GetData().GetAwaiter();
53         if (!taskAwaiter.IsCompleted)
54         {
55             this.<> 1__state = 2;
56             this.<> u__1 = taskAwaiter;
57             this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this);
58             return;
59         }
60  IL_157: 61         taskAwaiter.GetResult();
62         taskAwaiter = default(TaskAwaiter<string>);
63         Console.WriteLine("end");
64     }
65     catch (Exception exception)
66     {
67         this.<> 1__state = -2;
68         this.<> t__builder.SetException(exception);
69         return;
70     }
71     this.<> 1__state = -2;
72     this.<> t__builder.SetResult();
73 }

仍是比較容易理解,思路和單個await同樣,這裏經過goto的方式來控制流程,很聰明的作法,這樣既能夠跳轉,又不影響taskAwaiter.IsCompleted爲true時的直接運行。

在講AsyncVoidMethodBuilder.Create時講到SynchronizationContext的用處是處理異常,那如今來看看AsyncVoidMethodBuilder的異常處理:

 1 internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext)
 2 {
 3     var edi = ExceptionDispatchInfo.Capture(exception);
 4 
 5     if (targetContext != null)
 6     {
 7         try
 8         {
 9             targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi); 10             return;
11         }
12         catch (Exception postException)
13         {
14             edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
15         }
16     }
17 }

看到了吧,把異常經過targetContext.Post的方式給到最開始的線程,這也是爲何在Task外面的try..catch能抓到異步異常的緣由。

總結

好了,以上就是用IL來對async/await的分析,總結一下:

async/await本質上只是一個語法糖,它並不產生線程,只是在編譯時把語句的執行邏輯改了,至關於過去咱們用callback,這裏編譯器幫你作了。線程的轉換是經過SynchronizationContext來實現,若是作了Task.ConfigureAwait(false)操做,運行MoveNext時就只是在線程池中拿個空閒線程出來執行;若是Task.ConfigureAwait(true)-(默認),則會在異步操做前Capture當前線程的SynchronizationContext,異步操做以後運行MoveNext時經過SynchronizationContext轉到目標以前的線程。通常是想更新UI則須要用到SynchronizationContext,若是異步操做完成還須要作大量運算,則能夠考慮Task.ConfigureAwait(false)把計算放到後臺算,防止UI卡死。

另外還有在異步操做前作的ExecutionContext.FastCapture,獲取當前線程的執行上下文,注意,若是Task.ConfigureAwait(false),會有個IgnoreSynctx的標記,表示在ExecutionContext.Capture裏不作SynchronizationContext.Capture操做,Capture到的執行上下文用來在awaiter completed後給MoveNext用,使MoveNext能夠有和前面線程一樣的上下文。

經過SynchronizationContext.Post操做,可使異步異常在最開始的try..catch塊中輕鬆捕獲。

相關文章
相關標籤/搜索