接上篇:30分鍾?不需要,輕松讀懂IL,這篇主要從IL入手來理解async/await的工作原理。
先簡單介紹下async/await,這是.net 4.5引入的語法糖,配合Task使用可以非常優雅的寫異步操作代碼,它本身並不會去創建一個新線程,線程的工作還是由Task來做,async/await只是讓開發人員以直觀的方式寫異步操作代碼,而不像以前那樣到處都是callback或事件。
async/await IL翻譯
先寫個簡單的例子:
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個接口函數,我們整理一下狀態機代碼:
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 }
這樣就好看多了。
再來看看我們寫的DisplayDataAsync的IL:
雙擊![]()
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塊中輕松捕獲。

