接上篇:30分鐘?不須要,輕鬆讀懂IL,這篇主要從IL入手來理解async/await的工做原理。 html
先簡單介紹下async/await,這是.net 4.5引入的語法糖,配合Task使用能夠很是優雅的寫異步操做代碼,它自己並不會去建立一個新線程,線程的工做仍是由Task來作,async/await只是讓開發人員以直觀的方式寫異步操做代碼,而不像之前那樣處處都是callback或事件。 git
先寫個簡單的例子: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直譯,代碼可能有點冗餘,不過不傷大雅。
如今疏理一下,從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塊中輕鬆捕獲。