網上有太多關於task async await前世今生的帖子,我這里就直接進入主題吧,大概分以下幾個部分來簡單聊聊異步編程的原理實現。1.task執行源碼解讀,看看微軟底層對task的實現和thread有啥關系和區別。2.從il代碼層面看看編譯器對task和async await做了啥操作,以至於語法這么先進就能實現異步編程。3.為啥async修飾的方法返回值必須是規定的類型。4.解讀task async await執行邏輯。以上就是今天我要簡單聊的內容。
Task執行源碼解讀
首先非常感謝微軟擁抱開源這一壯舉啊,有興趣的朋友可以到github上面下載runtime源碼,地址:
https://github.com/dotnet/runtime 。還有一種方式,在線瀏覽源碼,地址
https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,c39f4253ff7cb1ee 。我們就從入口run & start這兩個方法開始吧,直接貼源碼,一步一步跟蹤,看看微軟對task的部分實現。
1 public static Task Run(Action action) 2 { 3 return InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default, TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None); 4 }
這就是我們經常調用task的run方法,在這個方法里面我們先留意一下TaskScheduler.Default這個對象,后續再說,我們繼續跟蹤執行主方法StartNew里面的實現。看代碼
1 internal static Task<TResult> InternalStartNew(Task parent, Func<TResult> function, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler) 2 { 3 // 其他代碼... 4 Task<TResult> task = new Task<TResult>(function, parent, cancellationToken, creationOptions, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler); 5 task.ScheduleAndStart(needsProtection: false); 6 return task; 7 }
在創建task實例時,指定了parent參數,默認是null,說明task是有層級關系的,這里需要注意task的返回時機,對我們后續理解awaiter有幫助,我們繼續跟蹤ScheduleAndStart方法。
1 internal void ScheduleAndStart(bool needsProtection) 2 { 3 // 其他代碼... 4 try 5 { 6 m_taskScheduler.InternalQueueTask(this); 7 } 8 catch (Exception innerException) 9 { 10 TaskSchedulerException ex = new TaskSchedulerException(innerException); 11 AddException(ex); 12 Finish(userDelegateExecute: false); 13 if ((Options & (TaskCreationOptions)512) == 0) 14 { 15 m_contingentProperties.m_exceptionsHolder.MarkAsHandled(calledFromFinalizer: false); 16 } 17 throw ex; 18 } 19 }
這里直接調用了TaskScheduler.Default這個對象的InternalQueueTask方法,上面我有說留意這個scheduler對象,最終我們的task就是通過這個scheduler得以執行,我們看看它的定義
1 public abstract class TaskScheduler 2 { 3 protected internal abstract void QueueTask(Task task); 4 5 // 其他成員.... 6 7 internal void InternalQueueTask(Task task) 8 { 9 QueueTask(task); 10 } 11 12 13 }
以上代碼我把其他成員全部刪了,它是一個抽象類,最終我們task的執行是由taskscheduler的實現類通過多態的方式得以執行,我們繼續看看它的默認實現類 ThreadPoolTaskScheduler的簡單定義。
1 internal sealed class ThreadPoolTaskScheduler : TaskScheduler 2 { 3 // 其他成員... 4 protected internal override void QueueTask(Task task) 5 { 6 TaskCreationOptions options = task.Options; 7 if ((options & TaskCreationOptions.LongRunning) != 0) 8 { 9 Thread thread = new Thread(s_longRunningThreadWork); 10 thread.IsBackground = true; 11 thread.Start(task); 12 } 13 else 14 { 15 bool preferLocal = (options & TaskCreationOptions.PreferFairness) == 0; 16 ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal); 17 } 18 } 19 }
代碼比較簡單,默認情況下我們的task是以threadpool的方式執行,由clr管理。當然你也可以通過設置參數以thread的方式執行。不知道是否有朋友跟我一樣,在剛接觸task的時候,調用task的run方法總比thread的start方法慢半拍甚至更長。task的執行就簡單說到這,接着我們簡單聊聊async & await結合task實現異步編程。
從IL代碼層面看看編譯器對task和async await做了啥操作?下面我簡單貼一下,我的測試代碼,沒有什么含義,重在看看編譯器做了啥。
1 static void Main(string[] args) 2 { 3 TestAsync(); 4 Console.WriteLine("Hello World!"); 5 } 6 7 8 public static async void TestAsync() 9 { 10 Console.WriteLine("start get string"); 11 var r1 = await GetDataString(); 12 Console.WriteLine(r1); 13 14 15 Console.WriteLine("end complete"); 16 } 17 18 19 public static async Task<string> GetDataString() 20 { 21 return await Task.Run<string>(async () => 22 { 23 { 24 await Task.Delay(5000); 25 return "get data complete"; 26 } 27 }); 28 }
以上測試代碼不用描述了吧,很簡單的代碼,沒啥意義,接下來我們通過ildasm工具來查看以上源碼編譯的il代碼。


上面我就截了一個圖,從圖來看還是比較犀利的,我們上面的代碼被編譯成這樣了,多了2個類型,d__這些,那么我們是否可以這么理解,編譯器在編譯過程中,如果發現方法有async修飾,就會被編譯成一個類型class。我們下面直接讀il代碼吧,從main函數開始。看代碼
1 .method private hidebysig static void Main(string[] args) cil managed 2 { 3 .entrypoint 4 // 代碼大小 19 (0x13) 5 .maxstack 8 6 IL_0000: nop 7 IL_0001: call void ConsoleApp2.Program::TestAsync() // 調用TestAsync() 8 IL_0006: nop 9 IL_0007: ldstr "Hello World!" // 加載字符串到Hello World! 10 IL_000c: call void [System.Console]System.Console::WriteLine(string) // 打印Hello World! 11 IL_0011: nop 12 IL_0012: ret // 退出 13 } // end of method Program::Main
il代碼的解說我直接在代碼后面注釋了,我們繼續看testasync方法里面的il代碼。
1 .method public hidebysig static void TestAsync() cil managed 2 { 3 .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( 01 00 23 43 6F 6E 73 6F 6C 65 41 70 70 32 2E 50 // ..#ConsoleApp2.P 4 72 6F 67 72 61 6D 2B 3C 54 65 73 74 41 73 79 6E // rogram+<TestAsyn 5 63 3E 64 5F 5F 31 00 00 ) // c>d__1.. 6 .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) 7 // 代碼大小 38 (0x26) 8 .maxstack 2 9 .locals init (class ConsoleApp2.Program/'<TestAsync>d__1' V_0) 聲明<TestAsync>d__1 v_0變量 10 IL_0000: newobj instance void ConsoleApp2.Program/'<TestAsync>d__1'::.ctor() // 創建<TestAsync>d__1實例 11 IL_0005: stloc.0 賦值到v_0 = new <TestAsync>d__1(); 12 IL_0006: ldloc.0 加載第一個變量 13 IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create() 調用v_0的create函數 14 IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 調用v_0的create函數的返回值賦值給<>t__builder 15 IL_0011: ldloc.0 16 IL_0012: ldc.i4.m1 加載int32數值-1 17 IL_0013: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把-1賦值給<>1__state 18 IL_0018: ldloc.0 19 IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 20 IL_001e: ldloca.s V_0 加載v_0地址 21 IL_0020: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<class ConsoleApp2.Program/'<TestAsync>d__1'>(!!0&) 調用V_0.<>t__builder.start方法並傳遞參數引用 ref V_0 22 IL_0025: ret 23 } // end of method Program::TestAsync
到此,我們的手擼代碼好像還沒看到,不知道哪里去了,il代碼的解說需要暫停一下,我們通過查看il代碼發現最終調用了AsyncVoidMethodBuilder的start方法,我這里繼續跟蹤start方法,看看start方法里面做了啥。
1 public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 2 { 3 // 其他代碼... 4 Thread currentThread = Thread.CurrentThread; 5 Thread thread = currentThread; 6 ExecutionContext executionContext = currentThread._executionContext; 7 ExecutionContext executionContext2 = executionContext; 8 SynchronizationContext synchronizationContext = currentThread._synchronizationContext; 9 try 10 { 11 stateMachine.MoveNext(); 12 } 13 finally 14 { 15 SynchronizationContext synchronizationContext2 = synchronizationContext; 16 Thread thread2 = thread; 17 if (synchronizationContext2 != thread2._synchronizationContext) 18 { 19 thread2._synchronizationContext = synchronizationContext2; 20 } 21 ExecutionContext executionContext3 = executionContext2; 22 ExecutionContext executionContext4 = thread2._executionContext; 23 if (executionContext3 != executionContext4) 24 { 25 ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4); 26 } 27 } 28 }
這里我們暫時先關注MoveNext方法的調用,stateMachine是start方法傳遞過來的參數,也就是說此處調用的moveNext方法就是,以上分析il代碼,編譯器為我們創建的<TestAsync>d__1對象實現的moveNext方法,感覺我的描述有點拗,繼續跟蹤又回到了il代碼,先看下編譯器為我們生成的類型<TestAsync>d__1的定義。
1 .class auto ansi sealed nested private beforefieldinit '<TestAsync>d__1' 2 extends [System.Runtime]System.Object 3 implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine 4 { 5 .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 6 } // end of class '<TestAsync>d__1'
其實就是一個IAsyncStateMachine,名叫狀態機。我們繼續看看moveNext函數。
1 .method private final hidebysig newslot virtual 2 instance void MoveNext () cil managed 3 { 4 5 .locals init ( 6 [0] int32, 7 [1] valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string>, 8 [2] class ConsoleApp2.Program/'<TestAsync>d__1', 9 [3] class [System.Runtime]System.Exception 10 ) 定義4個變量 11 12 13 IL_0000: ldarg.0 加載當前引用this 14 IL_0001: ldfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 加載<>1__state字段 15 IL_0006: stloc.0 把<>1__state賦值給第一個參數 16 .try try塊 17 { 18 IL_0007: ldloc.0 加載第一個參數 19 IL_0008: brfalse.s IL_000c 如果為0 或者 false 跳轉到IL_000c地址執行 20 IL_000a: br.s IL_000e 否則跳轉到IL_000e地址執行 21 IL_000c: br.s IL_0055 接上上面跳轉 22 IL_000e: nop 23 IL_000f: ldstr "start get string" 加載字符串到棧頂 24 IL_0014: call void [System.Console]System.Console::WriteLine(string) 打印棧頂字符串 25 IL_0019: nop 26 IL_001a: call class [System.Runtime]System.Threading.Tasks.Task`1<string> ConsoleApp2.Program::GetDataString() 調用GetDataString()函數 27 IL_001f: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [System.Runtime]System.Threading.Tasks.Task`1<string>::GetAwaiter() 通過多態的方式調用GetDataString()返回值task的GetAwaiter()函數 28 IL_0024: stloc.1 賦值給第二個變量 29 IL_0025: ldloca.s 1 加載第二個變量的地址 30 IL_0027: call instance bool valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string>::get_IsCompleted() 調用第二個變量地址的get屬性,獲取IsCompleted屬性的值 31 IL_002c: brtrue.s IL_0071 返回值為true 跳轉到IL_0071 32 33 34 IL_002e: ldarg.0 如果為false,接着往下走, 加載this指針 35 IL_002f: ldc.i4.0 加載int32 0到棧頂 36 IL_0030: dup 復制備份 37 IL_0031: stloc.0 存儲到第一個變量 38 IL_0032: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把第一個變量的值0 復制給<>1__state字段 39 IL_0037: ldarg.0 加載this指針 40 IL_0038: ldloc.1 加載第二個變量 41 IL_0039: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string> ConsoleApp2.Program/'<TestAsync>d__1'::'<>u__1' 把第二個變量復制到<>u__1字段 42 IL_003e: ldarg.0 this指針 43 IL_003f: stloc.2 把this賦值給第三個變量 44 IL_0040: ldarg.0 加載this 45 IL_0041: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 加載<>t__builder字段 46 IL_0046: ldloca.s 1 加載第二個變量地址 47 IL_0048: ldloca.s 2 加載三個變量地址 你可以理解為 為下一個操作傳遞引用ref 48 IL_004a: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::AwaitUnsafeOnCompleted<valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string>, class ConsoleApp2.Program/'<TestAsync>d__1'>(!!0&, !!1&) 調用<>t__builder.AwaitUnsafeOnCompleted函數並通過ref傳遞第二三個變量 49 IL_004f: nop 50 IL_0050: leave IL_00e4 return出去 51 52 53 IL_0055: ldarg.0 加載this 54 IL_0056: ldfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string> ConsoleApp2.Program/'<TestAsync>d__1'::'<>u__1' 加載字段<>u__1 55 IL_005b: stloc.1 把<>u__1字段賦值給第二參數 56 IL_005c: ldarg.0 加載this 57 IL_005d: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string> ConsoleApp2.Program/'<TestAsync>d__1'::'<>u__1' 加載<>u__1字段 58 IL_0062: initobj valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string> 初始化<>u__1字段 59 IL_0068: ldarg.0 加載this 60 IL_0069: ldc.i4.m1 加載int32 -1 61 IL_006a: dup 復制 62 IL_006b: stloc.0 存儲到第一個變量 63 IL_006c: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 同事把第一個變量賦值到<>1__state字段 64 65 66 IL_0071: ldarg.0 加載this 67 IL_0072: ldloca.s 1 加載第二個變量地址 68 IL_0074: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string>::GetResult() 調用第二個變量的GetResult() 函數 69 IL_0079: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<>s__2' 把返回值賦值給<>s__2字段 70 IL_007e: ldarg.0 71 IL_007f: ldarg.0 72 IL_0080: ldfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<>s__2' 加載<>s__2字段 73 IL_0085: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<r1>5__1' 把<>s__2字段賦值給<r1>5__1字段 74 IL_008a: ldarg.0 75 IL_008b: ldnull 加載null 76 IL_008c: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<>s__2' 把null賦值給<>s__2字段 77 IL_0091: ldarg.0 78 IL_0092: ldfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<r1>5__1' 加載字段<r1>5__1 79 IL_0097: call void [System.Console]System.Console::WriteLine(string) 打印字段<r1>5__1 80 IL_009c: nop 81 IL_009d: ldstr "end complete" 加載字符串end complete 82 IL_00a2: call void [System.Console]System.Console::WriteLine(string) 打印字符串 83 IL_00a7: nop 84 IL_00a8: leave.s IL_00c9 return彈棧 85 } // end .try 86 catch [System.Runtime]System.Exception 87 { 88 IL_00aa: stloc.3 把exception賦值給第四個參數 89 IL_00ab: ldarg.0 90 IL_00ac: ldc.i4.s -2 加載-2 91 IL_00ae: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把-2賦值給<>1__state字段 92 IL_00b3: ldarg.0 93 IL_00b4: ldnull 加載null 94 IL_00b5: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<r1>5__1' 把null賦值給<r1>5__1字段 95 IL_00ba: ldarg.0 96 IL_00bb: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 加載字段<>t__builder 97 IL_00c0: ldloc.3 加載4個變量 98 IL_00c1: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetException(class [System.Runtime]System.Exception) 調用<>t__builder字段的SetException函數 99 IL_00c6: nop 100 IL_00c7: leave.s IL_00e4 彈棧 101 } // end handler 102 103 104 IL_00c9: ldarg.0 105 IL_00ca: ldc.i4.s -2 加載-2 106 IL_00cc: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把-2賦值給<>1__state字段 107 IL_00d1: ldarg.0 108 IL_00d2: ldnull 109 IL_00d3: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<r1>5__1' 把null賦值給<r1>5__1 110 IL_00d8: ldarg.0 111 IL_00d9: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 加載<>t__builder字段 112 IL_00de: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetResult() 調用<>t__builder字段的SetResult()函數 113 IL_00e3: nop 114 115 116 IL_00e4: ret 彈棧 117 } // end of method '<TestAsync>d__1'::MoveNext
以上il代碼就是moveNext方法的執行邏輯,其實讀il代碼我們要按cpu執行機器碼指令去解讀,當然這不是真正的cpu指令,但是你真正去逆向非托管代碼,其執行指令解讀方式都差不多,但是難度比這個大,本人之前簡單逆向了加密狗的lincens,當然那個比較簡單,稍微復雜點我也搞不定。我們知道非托管代碼編譯是丟棄了源碼,直接編譯成本地機器碼,高手是可以通過讀機器碼還原大部分源代碼,還原不是目的,目的是破解,好了,牛逼不吹了,繼續我們的任務。通過以上il代碼,我們簡單還原一下部分代碼。
1 class <TestAsync>d__1{ 2 int32 '<>1__state' 3 AsyncVoidMethodBuilder '<>t__builder' 4 string '<r1>5__1' 5 string '<>s__2' 6 TaskAwaiter<string> '<>u__1' 7 void MoveNext () 8 { 9 int num; 10 TaskAwaiter<string> awaiter ; 11 <TestAsync>d__1 stateMachine; 12 Exception exception; 13 num = <>1__state; 14 try 15 { 16 if (num != 0) 17 { 18 Console.WriteLine("start get string"); 19 awaiter = GetDataString().GetAwaiter(); 20 if (!awaiter.IsCompleted) 21 { 22 num = 0; 23 <>1__state =0; 24 <>u__1 = awaiter; 25 <TestAsync>d__1 stateMachine = this; 26 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 27 return; 28 } 29 } 30 else{ 31 awaiter = <>u__1; 32 <>u__1 = default(TaskAwaiter<string>); 33 num = (<>1__state = -1); 34 <>s__2 = awaiter.GetResult(); 35 36 <r1>5__1 = <>s__2; 37 <>s__2 = null; 38 Console.WriteLine(<r1>5__1); 39 Console.WriteLine("end complete"); 40 return; 41 } 42 } 43 catch() 44 { 45 // todo.... 46 } 47 } 48 }
以上就是針對il代碼做的簡單還原,你也可以理解為偽代碼,在這個調用堆棧里面只有兩個await,所以在狀態機里面只有兩個狀態,這以為着await越多,狀態就越多,邏輯就越復雜。簡單總結下,1.編譯器會把async修飾的方法,編譯成class並派生自IAsyncStateMachine,2.await決定狀態機里面有多少個狀態,當然在movenext范圍內。好了il代碼就看到這里,還原不是目的,目的是理清執行邏輯以及在邏輯里面涉及到的一些底層的實現。有了執行邏輯,接下來我們看看,微軟到底干了啥?
async & await執行,接着上面源碼跟蹤的start方法,在start方法里面,執行了moveNext方法。moveNext方法的實現邏輯以及涉及到的對象,我們也通過il部分還原了。回顧一下start方法,怕大家忘記,我這里從新貼一下代碼
1 .method public hidebysig static void TestAsync() cil managed 2 { 3 4 .maxstack 2 5 .locals init (class ConsoleApp2.Program/'<TestAsync>d__1' V_0) 聲明<TestAsync>d__1 v_0變量 6 IL_0000: newobj instance void ConsoleApp2.Program/'<TestAsync>d__1'::.ctor() // 創建<TestAsync>d__1實例 7 IL_0005: stloc.0 賦值到v_0 = new <TestAsync>d__1(); 8 IL_0006: ldloc.0 加載第一個變量 9 IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create() 調用v_0的create函數 10 IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 調用v_0的create函數的返回值賦值給<>t__builder 11 IL_0011: ldloc.0 12 IL_0012: ldc.i4.m1 加載int32數值-1 13 IL_0013: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把-1賦值給<>1__state 14 IL_0018: ldloc.0 15 IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 16 IL_001e: ldloca.s V_0 加載v_0地址 17 IL_0020: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<class ConsoleApp2.Program/'<TestAsync>d__1'>(!!0&) 調用V_0.<>t__builder.start方法並傳遞參數引用 ref V_0 18 IL_0025: ret 19 } // end of method Program::TestAsync
最終通過調用<>t__builder.start方法啟動入口,<>t__builder類型為AsyncVoidMethodBuilder,我們先看下它的定義。
1 public struct AsyncVoidMethodBuilder 2 { 3 // 其他成員... 4 private SynchronizationContext _synchronizationContext; 5 private AsyncTaskMethodBuilder _builder; 6 private Task Task => _builder.Task; 7 internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger; 8 public static AsyncVoidMethodBuilder Create() 9 { 10 SynchronizationContext current = SynchronizationContext.Current; 11 current?.OperationStarted(); 12 AsyncVoidMethodBuilder result = default(AsyncVoidMethodBuilder); 13 result._synchronizationContext = current; 14 return result; 15 } 16 17 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 18 { 19 AsyncMethodBuilderCore.Start(ref stateMachine); 20 } 21 }
它實際就是一個結構體,除了它,還有其他兩個結構體,分別為AsyncTaskMethodBuilder,AsyncTaskMethodBuilder<T>,其實看命名就大概能猜到了,根據返回值的不同而不同,這個后面會細說,我們繼續start方法。通過調用AsyncMethodBuilderCore對象的同名函數start實現,在AsyncMethodBuilderCore.start方法里面調用了我們上面還原的moveNext方法。我們重點看movenext方法。
1 if (num != 0) 2 { 3 Console.WriteLine("start get string"); 4 awaiter = GetDataString().GetAwaiter(); 5 if (!awaiter.IsCompleted) 6 { 7 num = 0; 8 <>1__state =0; 9 <>u__1 = awaiter; 10 <TestAsync>d__1 stateMachine = this; 11 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 12 return; 13 } 14 }
第一次調用,num為-1,也就是!=0,打印字符串,接着獲取awaiter對象,這個地方需要注意一下,由於我在GetDataString方法里面啟動了新的task,結合上面解說的task執行邏輯,在task還未真正執行就返回這個task,並且獲得該task的awaiter對象。如果我在GetDataString沒有開啟task,那么就會同步執行,並且不會自己去開新線程。我們繼續看,接着判斷awaiter.IsCompleted,實際就是獲取task的狀態,這里我們延時5s,正常返回的是false,接着執行AwaitUnsafeOnCompleted方法。我們繼續看AwaitUnsafeOnCompleted方法的實現邏輯。
1 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine 2 { 3 IAsyncStateMachineBox stateMachineBox = GetStateMachineBox(ref stateMachine); // 有興趣的朋友自己去看源碼,我這里簡單描述一下,整個這一塊還是比較復雜的,包裝當前狀態機<TestAsync>d__1以及當前線程上下文,這個線程在console里面叫主線程上下文,以及初始化MoveNextAction,最后hook等待狀態機的狀態變化. 4 if (default(TAwaiter) != null && awaiter is ITaskAwaiter) // 默認我們走的這個分支 5 { 6 TaskAwaiter.UnsafeOnCompletedInternal(Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter).m_task, stateMachineBox, continueOnCapturedContext: true); 7 } 8 else if (default(TAwaiter) != null && awaiter is IConfiguredTaskAwaiter) 9 { 10 11 } 12 else if (default(TAwaiter) != null && awaiter is IStateMachineBoxAwareAwaiter) 13 { 14 15 } 16 else 17 { 18 19 } 20 }
上面代碼涉及到邏輯的地方,我在后面有相關注釋,這里我還是貼一下IAsyncStateMachineBox接口的定義.
1 internal interface IAsyncStateMachineBox 2 { 3 Action MoveNextAction // 默認綁定到moveNext 4 { 5 get; 6 } 7 8 9 void MoveNext(); // 狀態機狀態發生變更 10 11 12 IAsyncStateMachine GetStateMachineObject(); 13 }
通過上面代碼跟蹤發現,任務狀態已經變更(此處為完成,如果多個await會有多個狀態),接下來的操作是不是要執行await之后的邏輯?我們繼續看看TaskAwaiter.UnsafeOnCompletedInternal的實現.
1 internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext) 2 { 3 if (continueOnCapturedContext) 4 { 5 SynchronizationContext current = SynchronizationContext.Current; //獲取當前線程 6 if (current != null && current.GetType() != typeof(SynchronizationContext)) 7 { 8 SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = new SynchronizationContextAwaitTaskContinuation(current, stateMachineBox.MoveNextAction, flowExecutionContext: false); 9 if (!AddTaskContinuation(synchronizationContextAwaitTaskContinuation, addBeforeOthers: false)) 10 { 11 synchronizationContextAwaitTaskContinuation.Run(this, canInlineContinuationTask: false); // 此處調用run 12 } 13 return; 14 } 15 TaskScheduler internalCurrent = TaskScheduler.InternalCurrent; 16 if (internalCurrent != null && internalCurrent != TaskScheduler.Default) 17 { 18 TaskSchedulerAwaitTaskContinuation taskSchedulerAwaitTaskContinuation = new TaskSchedulerAwaitTaskContinuation(internalCurrent, stateMachineBox.MoveNextAction, flowExecutionContext: false); 19 if (!AddTaskContinuation(taskSchedulerAwaitTaskContinuation, addBeforeOthers: false)) 20 { 21 taskSchedulerAwaitTaskContinuation.Run(this, canInlineContinuationTask: false); 22 } 23 return; 24 } 25 } 26 if (!AddTaskContinuation(stateMachineBox, addBeforeOthers: false)) 27 { 28 ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, preferLocal: true); 29 } 30 }
我們繼續跟蹤synchronizationContextAwaitTaskContinuation.Run方法.
1 internal sealed override void Run(Task task, bool canInlineContinuationTask) 2 { 3 if (canInlineContinuationTask && m_syncContext == SynchronizationContext.Current) // 判斷線程上下文環境,如果當前線程和前面設置的線程上下文環境一致,后續的操作還是由當前線程處理,為什么有這個需求?cs開發的朋友知道,有個線程安全問題 4 { 5 RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), m_action, ref Task.t_currentTask); // 注意這個m_action,是前面綁定的moveNext.action, GetInvokeActionCallback方法 6 return; 7 } 8 TplEventSource log = TplEventSource.Log; 9 if (log.IsEnabled()) 10 { 11 m_continuationId = Task.NewId(); 12 log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId); 13 } 14 RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask); 15 }
以上代碼最終調用的是RunCallback方法,繼續看.
1 private static readonly ContextCallback s_invokeContextCallback = delegate(object state) // GetInvokeActionCallback方法的實現 2 { 3 ((Action)state)(); 4 }; 5 6 protected void RunCallback(ContextCallback callback, object state, ref Task currentTask) 7 { 8 Task task = currentTask; 9 try 10 { 11 if (task != null) 12 { 13 currentTask = null; 14 } 15 ExecutionContext capturedContext = m_capturedContext; 16 if (capturedContext == null) 17 { 18 callback(state); // 回調 19 } 20 else 21 { 22 ExecutionContext.RunInternal(capturedContext, callback, state); 23 } 24 } 25 catch (Exception exception) 26 { 27 Task.ThrowAsync(exception, null); 28 } 29 finally 30 { 31 if (task != null) 32 { 33 currentTask = task; 34 } 35 } 36 }
整個邏輯走到這,算基本走完了吧.如果不需要線程上下文切換,直接回調 AsyncStateMachineBox .moveNext.Action,由於它綁定到了 AsyncStateMachineBox .MoveNext,而 AsyncStateMachineBox.moveNext里面直接調用了 StateMachine.MoveNext(),這時我們的狀態機movenext發生第二次調用並且繼續執行await后面的邏輯.這里我簡單貼一下AsyncStateMachineBox代碼,它派生自 IAsyncStateMachineBox,它的初始化是在等待狀態機狀態變化時初始化.
1 private class AsyncStateMachineBox<TStateMachine> : Task<TResult>, IAsyncStateMachineBox where TStateMachine : notnull, IAsyncStateMachine 2 { 3 4 public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext)); 5 6 7 public void MoveNext() 8 { 9 MoveNext(null); 10 } 11 其他成員.... 12 13 private void MoveNext(Thread threadPoolThread) 14 { 15 // 其他代碼.... 16 ExecutionContext context = Context; 17 if (context == null) 18 { 19 StateMachine.MoveNext(); 20 } 21 else if (threadPoolThread == null) 22 { 23 ExecutionContext.RunInternal(context, s_callback, this); 24 } 25 else 26 { 27 ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, context, s_callback, this); 28 } 29 } 30 }
最后,接着執行我們還原的那部分movenext代碼,只不過是else部分.簡單總結一下,在movenext調用堆棧里面有幾個await就會有幾個狀態,第一次-1狀態,獲取awaiter對象,如果async方法里面有開啟新線程,此時主線程會返回執行主線程自己的邏輯,接着判斷任務是否完成,如果未完成,調用AwaitUnsafeOnCompleted方法,在該方法里面hook狀態機狀態,如果狀態發生變更,封裝狀態機和awaiter以及線程上下文並且繼續回調我們的movenext方法,此時如果movenext里面只有一個await,表示任務已完成,直接從awaiter里面獲取結果,這里有個地方需要注意,如果需要線程上下文切換,會調用context的post方法完成.
1 else{ 2 awaiter = <>u__1; 3 <>u__1 = default(TaskAwaiter<string>); 4 num = (<>1__state = -1); 5 <>s__2 = awaiter.GetResult(); 6 7 8 <r1>5__1 = <>s__2; 9 <>s__2 = null; 10 Console.WriteLine(<r1>5__1); 11 Console.WriteLine("end complete"); 12 return; 13 }
感覺異步編程都是圍繞Task這個對象在開展,也走讀了task源碼,實現還是比較復雜,好了就到這.以上僅供參考.