在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
IAsyncStateMachine.MoveNext方法的线程何时发起的? 那么今天就来尝试解决它们吧~ 一、哪里来的线程? 通过上一篇随笔的调查我们知道了,async标记的方法的方法体会被编译到一个内部结构体的MoveNext方法中,并且也找到了MoveNext的调用者,再且也证实了有两个调用者是来自于主线程之外的同一个工作线程。 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 else 25 { 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>。 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_coreState 11 .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的构建细节吧: 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 else 25 { 26 this.m_defaultContextAction = action; 27 } 28 } 29 else 30 { 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时经过的分支。 第40行的赋值不影响Action中的Run,只是在头尾追加了状态记录的操作。 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 else 19 { 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 finally 10 { 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,运行起来效果是这样的: HERE
THERE
From 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: 到此为止算是搞定第一个问题了。 二、lambda为何先行? 先来回忆一下GetHere方法的内容: // 三、理解await Task<string> GetHere() { return Task.Run(() => { Thread.Sleep(1000); return "HERE"; }); } 要追踪的lambda就是在这里构造的,而调用GetHere的地方也只有一个,就是MoveNext方法的try块。 其中Start方法是在主线程中调用的,可以由SampleMethod追溯到。那么以下的调用信息: Function: Test.Program.Main(string[]), Thread: 0xE88 主线程 Function: Test.Program.GetHere.AnonymousMethod__3(), Thread: 0x37DC 工作线程 Function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run(), Thread: 0x37DC 工作线程 Function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(object), Thread: 0x37DC 工作线程 这个顺序不是有点奇怪吗?lambda怎么能先于MoveNextRunner的两个方法执行? 子线程中的lambda是来源于主线程第一次调用的MoveNext,和之后的Run啊InvokeMoveNext是没有关系的,所以这个顺序也就不奇怪了。 三、MoveNext干了什么? 第二个问题虽然解决了,但是也让第三个问题显得更加重要,既然lambda确实是先于MoveNext,那么MoveNext到底做了些什么? 回头看本文的第一段代码,前后两次进入同一段代码,但是做了不同的事情,那么显然就是两次走了不同的分支咯。 来看看执行结果如何吧: Function: Test.Program.SampleMethod(), Thread: 0x9BC 主线程 Function: System.Threading.Tasks.Task<TResult>.GetAwaiter(), Thread: 0x9BC 主线程 Function: System.Runtime.CompilerServices.AsyncVoidMethodBuilder.AwaitUnsafeOnCompleted<TAwaiter,TStateMachine>(ref TAwaiter, ref TStateMachine), Thread: 0x9BC 主线程 Function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(object), Thread: 0x3614 工作线程 Function: System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult(), Thread: 0x3614 工作线程 需要注意的是,断到InvokeMoveNext里头的时候,只有这一行代码: ((IAsyncStateMachine)stateMachine).MoveNext(); 而当我按下F11步入之后,可以猜一猜跳到了哪: async void SampleMethod() { Console.WriteLine(await GetHere()); } 而在这个时候GetResult还没执行到。 1 try 2 { 3 '<>t__doFinallyBodies' = true; 4 CS$0$0000 = this.'<>1__state'; 5 if (CS$0$0000 != 0) 6 { 7 CS$0$0001 = this.'<>4__this'.GetHere().GetAwaiter(); 8 if (!CS$0$0001.IsCompleted) 9 { 10 this.'<>1__state' = 0; 11 this.'<>u__$awaiter1' = CS$0$0001; 12 this.'<>t__builder'.AwaitUnsafeOnCompleted(ref CS$0$0001, ref this); 13 '<>t__doFinallyBodies' = false; 14 return; 15 } 16 } 17 else 18 { 19 CS$0$0001 = this.'<>u__$awaiter1'; 20 this.'<>u__$awaiter1' = CS$0$0002; 21 this.'<>1__state' = -1; 22 } 23 24 Console.WriteLine(CS$0$0001.GetResult()); 25 } 红字是第一次经过的分支,黄底是第二次经过的分支。 四、水落石出 async和await的轮廓逐渐清晰了~再结合上一篇的一段代码来看看: // 二、理解async void MoveNext() { bool local0; Exception local1; try { local0 = true; Thread.Sleep(1000); Console.WriteLine("HERE"); } catch (Exception e) { local1 = e; this.'<>1__state' = -2; this.'<>t__builder'.SetException(local1); return; } this.'<>1__state' = -2; this.'<>t__builder'.SetResult() } 黄底的两句代码原本是在哪的还记得吗?看这里: // 二、理解async async void SampleMethod() { Thread.Sleep(1000); Console.WriteLine("HERE"); } 因为这个async方法中没有出现await调用,所以可以认为仅有的两句代码是出现在await操作之前。 async void SampleMethod() { Console.WriteLine("WHERE"); Console.WriteLine(await GetHere()); } 再看看现在的MoveNext方法: 1 try 2 { 3 '<>t__doFinallyBodies' = true; 4 CS$0$0000 = this.'<>1__state'; 5 if (CS$0$0000 != 0) 6 { 7 Console.WriteLine("WHERE"); 8 CS$0$0001 = this.'<>4__this'.GetHere().GetAwaiter(); 9 if (!CS$0$0001.IsCompleted) 10 { 11 this.'<>1__state' = 0; 12 this.'<>u__$awaiter1' = CS$0$0001; 13 this.'<>t__builder'.AwaitUnsafeOnCompleted(ref CS$0$0001, ref this); 14 '<>t__doFinallyBodies' = false; 15 return; 16 } 17 } 18 else 19 { 20 CS$0$0001 = this.'<>u__$awaiter1'; 21 this.'<>u__$awaiter1' = CS$0$0002; 22 this.'<>1__state' = -1; 23 } 24 25 Console.WriteLine(CS$0$0001.GetResult()); 26 } 这样就可以很明显的看出来await前后的代码被放到了两个区块里,而这两个区块,也就是之前看到的两次执行MoveNext走过的分支。 最终调查结果如下: |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论