上一篇隨筆留下了幾個問題沒能解決:· 調用IAsyncStateMachine.MoveNext方法的線程何時發起的?· lambda的執行為何先于MoveNext方法?· 后執行的MoveNext方法做了些什么事情?
那么今天就來嘗試解決它們吧~PS: 本文中部分代碼來自上一篇隨筆,具體來源可參考注釋中的章節標題
一、哪里來的線程?
通過上一篇隨筆的調查我們知道了,async標記的方法的方法體會被編譯到一個內部結構體的MoveNext方法中,并且也找到了MoveNext的調用者,再且也證實了有兩個調用者是來自于主線程之外的同一個工作線程。可是這一個線程是何時發起的呢?上一次調查時沒能找到答案,這一次就繼續從MoveNext方法開始,先找找看Task相關的操作有哪些。
1 // 三、理解await 2 bool '<>t__doFinallyBodies'; 3 Exception '<>t__ex'; 4 int CS$0$0000; 5 TaskAwaiter<string> CS$0$0001; 6 TaskAwaiter<string> CS$0$0002; 7 8 try 9 {10 '<>t__doFinallyBodies' = true;11 CS$0$0000 = this.'<>1__state';12 if (CS$0$0000 != 0)13 {14 CS$0$0001 = this.'<>4__this'.GetHere().GetAwaiter();15 if (!CS$0$0001.IsCompleted)16 {17 this.'<>1__state' = 0;18 this.'<>u__$awaiter1' = CS$0$0001;19 this.'<>t__builder'.AwaitUnsafeOnCompleted(ref CS$0$0001, ref this);20 '<>t__doFinallyBodies' = false;21 return;22 }23 }24 else25 {26 CS$0$0001 = this.'<>u__$awaiter1';27 this.'<>u__$awaiter1' = CS$0$0002;28 this.'<>1__state' = -1;29 }30 31 Console.WriteLine(CS$0$0001.GetResult());32 }
注意到14行的GetHere方法返回了一個Task<string>,隨后的GetAwaiter返回的是TaskAwaiter<string>。不過這兩個Get方法都沒有做什么特別的處理,那么就看看接下來是誰使用了TaskAwaiter<string>實例。于是就來看看19行的AsyncVoidMethodBuilder.AwaitUnsafeOnCompleted里面做了些什么吧。
1 // System.Runtime.CompilerServices.AsyncVoidMethodBuilder 2 [__DynamicallyInvokable, SecuritySafeCritical] 3 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( 4 ref TAwaiter awaiter, ref TStateMachine stateMachine) 5 where TAwaiter : ICriticalNotifyCompletion 6 where TStateMachine : IAsyncStateMachine 7 { 8 try 9 {10 Action completionAction = this.m_coreState11 .GetCompletionAction<AsyncVoidMethodBuilder, TStateMachine>(ref this, ref stateMachine);12 awaiter.UnsafeOnCompleted(completionAction);13 }14 catch (Exception exception)15 {16 AsyncMethodBuilderCore.ThrowAsync(exception, null);17 }18 }
這里主要做了兩件事:一是創建了一個Action,MoveNext方法的信息已經隨著stateMachine被封裝進去了。二是把上面這個Action交給Awaiter,讓它在await的操作完成后執行這個Action。
先來看看Action的構建細節吧:
1 // System.Runtime.CompilerServices.AsyncMethodBuilderCore 2 [SecuritySafeCritical] 3 internal Action GetCompletionAction<TMethodBuilder, TStateMachine>(ref TMethodBuilder builder, ref TStateMachine stateMachine) 4 where TMethodBuilder : IAsyncMethodBuilder 5 where TStateMachine : IAsyncStateMachine 6 { 7 Debugger.NotifyOfCrossThreadDependency(); 8 ExecutionContext executionContext = ExecutionContext.FastCapture(); 9 Action action;10 AsyncMethodBuilderCore.MoveNextRunner moveNextRunner;11 if (executionContext != null && executionContext.IsPReAllocatedDefault)12 {13 action = this.m_defaultContextAction;14 if (action != null)15 {16 return action;17 }18 moveNextRunner = new AsyncMethodBuilderCore.MoveNextRunner(executionContext);19 action = new Action(moveNextRunner.Run);20 if (AsyncCausalityTracer.LoggingOn)21 {22 action = (this.m_defaultContextAction = this.OutputAsyncCausalityEvents<TMethodBuilder>(ref builder, action));23 }24 else25 {26 this.m_defaultContextAction = action;27 }28 }29 else30 {31 moveNextRunner = new AsyncMethodBuilderCore.MoveNextRunner(executionContext);32 action = new Action(moveNextRunner.Run);33 if (AsyncCausalityTracer.LoggingOn)34 {35 action = this.OutputAsyncCausalityEvents<TMethodBuilder>(ref builder, action);36 }37 }38 if (this.m_stateMachine == null)39 {40 builder.PreBoxInitialization<TStateMachine>(ref stateMachine);41 this.m_stateMachine = stateMachine;42 this.m_stateMachine.SetStateMachine(this.m_stateMachine);43 }44 moveNextRunner.m_stateMachine = this.m_stateMachine;45 return action;46 }
這段的分支有點多,行號上的標記是我DEBUG時經過的分支。可以看到,這個方法里面出現了MoveNext方法的調用者MoveNextRunner,它的Run方法被封裝到了返回的Action里。也就是說,只要這個Action被執行,就會進入Run方法,而Run方法里面有兩條分支,簡單來說就是:1.直接調用MoveNext2.通過InvokeMoveNext調用MoveNext
第40行的賦值不影響Action中的Run,只是在頭尾追加了狀態記錄的操作。接下來就趕緊找一找執行這個Action的地方吧!深入UnsafeOnCompleted方法,最終可以找到如下的方法,第一個參數就是要跟蹤的對象:
1 // System.Threading.Tasks.Task 2 [SecurityCritical] 3 internal void SetContinuationForAwait( 4 Action continuationAction, 5 bool continueOnCapturedContext, 6 bool flowExecutionContext, 7 ref StackCrawlMark stackMark) 8 { 9 TaskContinuation taskContinuation = null;10 if (continueOnCapturedContext)11 {12 SynchronizationContext currentNoFlow = SynchronizationContext.CurrentNoFlow;13 if (currentNoFlow != null && currentNoFlow.GetType() != typeof(SynchronizationContext))14 {15 taskContinuation = new SynchronizationContextAwaitTaskContinuation(16 currentNoFlow, continuationAction, flowExecutionContext, ref stackMark);17 }18 else19 {20 TaskScheduler internalCurrent = TaskScheduler.InternalCurrent;21 if (internalCurrent != null && internalCurrent != TaskScheduler.Default)22 {23 taskContinuation = new TaskSchedulerAwaitTaskContinuation(24 internalCurrent, continuationAction, flowExecutionContext, ref stackMark);25 }26 }27 }28 if (taskContinuation == null && flowExecutionContext)29 {30 taskContinuation = new AwaitTaskContinuation(continuationAction, true, ref stackMark);31 }32 if (taskContinuation != null)33 {34 if (!this.AddTaskContinuation(taskContinuation, false))35 {36 taskContinuation.Run(this, false);37 return;38 }39 }40 else if (!this.AddTaskContinuation(continuationAction, false))41 {42 AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);43 }44 }
同樣的,行號的標記意味著經過的分支。繼續跟進:
1 // System.Threading.Tasks.AwaitTaskContinuation 2 [SecurityCritical] 3 internal static void UnsafeScheduleAction(Action action, Task task) 4 { 5 AwaitTaskContinuation awaitTaskContinuation = new AwaitTaskContinuation(action, false); 6 TplEtwProvider log = TplEtwProvider.Log; 7 if (log.IsEnabled() && task != null) 8 { 9 awaitTaskContinuation.m_continuationId = Task.NewId();10 log.AwaitTaskContinuationScheduled(11 (task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id,12 task.Id,13 awaitTaskContinuation.m_continuationId);14 }15 ThreadPool.UnsafeQueueCustomWorkItem(awaitTaskContinuation, false);16 }
1 // System.Threading.ThreadPool 2 [SecurityCritical] 3 internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal) 4 { 5 ThreadPool.EnsureVMInitialized(); 6 try 7 { 8 } 9 finally10 {11 ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);12 }13 }
這里出現了全局線程池,然而沒有找到MSDN對ThreadPoolGlobals的解釋,這里頭的代碼又實在太多了。。。暫且模擬一下看看:
1 Console.WriteLine("HERE");2 var callback = new WaitCallback(state => Println("From ThreadPool"));3 ThreadPool.QueueUserWorkItem(callback);4 Console.WriteLine("THERE");
QueueUserWorkItem方法內部調用了ThreadPoolGlobals.workQueue.Enqueue,運行起來效果是這樣的:
HERETHEREFrom ThreadPool
再看看線程信息:
Function: CsConsole.Program.Main(), Thread: 0x2E58 主線程Function: CsConsole.Program.Main(), Thread: 0x2E58 主線程Function: CsConsole.Program.Main.AnonymousMethod__6(object), Thread: 0x30EC 工作線程
和async的表現簡直一模一樣是不是~?從調用堆棧也可以看到lambda的執行是源于這個workQueue:
到此為止算是搞定第一個問題了。
二、lambd
新聞熱點
疑難解答