分享
 
 
 

[C#]async和await刨根问底

王朝学院·作者佚名  2016-05-20
窄屏简体版  字體: |||超大  

[C#]async和await刨根问底上一篇随笔留下了几个问题没能解决:· 调用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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有