进阶篇:以IL为剑,直指async/await
2016-03-04 10:02
459 查看
接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理。
先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的写异步操作代码,它本身并不会去创建一个新线程,线程的工作还是由Task来做,async/await只是让开发人员以直观的方式写异步操作代码,而不像以前那样到处都是callback或事件。
编译: 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个接口函数,我们整理一下状态机代码:
这样就好看多了。
再来看看我们写的DisplayDataAsync的IL:
双击
好了,这个函数的意思差不多搞懂了,我们先把它翻译成容易看懂的C#代码,大概是这个样子:
与源代码完全不一样。
GetDataAsyncStateMachine还有两个接口函数的IL需要看下,接下来先看看这两个函数SetStateMachine和MoveNext的IL代码,把它也翻译过来,注意:IL里用的<DisplayDataAsync>d_1,<>1_state,<>_builder,<>u_1都可以用GetDataAsyncStateMachine,State, Builder,_taskAwaiter来表示了,这样更容易理解一些。
MoveNext:
SetStateMachine:
因为是照着IL直译,代码可能有点冗余,不过不伤大雅。
那重点就是这个AsyncVoidMethodBuilder的作用,AsyncVoidMethodBuilder在命名空间System.Runtime.CompilerServices下,我们来读一下它的源码,.net的BCL已经开源了,所以直接去github上找就行了。
这文件里面有这么几个重要类AsyncVoidMethodBuilder,AsyncTaskMethodBuilder,AsyncTaskMethodBuilder<T>,AsyncMethodBuilderCore及AsyncMethodBuilderCore内的MoveNextRunner。
首先为什么DsiplayDataAsync用到的是AsyncVoidMethodBuilder,因为DisplayDataAsync返回的是void,在ildasm里双击GetData你会发现如下IL:
GetData用的是AsyncTaskMethodBuilder<string>,因为GetData返回的是Task<string>。那我们就知道了,AsyncVoidMethodBuilder,AsyncTaskMethodBuilder,AsyncTaskMethodBuilder<T>这三个类分别对应返回为void, Task和Task<T>的异步函数,因为async标记的函数只能返回这三种类型。这三个类的功能差不多,代码大同小异,我们就拿用到的AsyncVoidMethodBuilder来说。
先看最先调用的Create()函数:
SynchronizationContext.CurrentNoFlow作用是取得当前线程的SynchronizationContext,这个有什么用呢,SynchronizationContext可以算是一个抽象概念的类(这个类本身不是抽象的),它提供了线程间通讯的桥梁,一般线程的SynchronizationContext.Current为空,但主线程除外,比如对于WinForm,在第一个窗体创建时,系统会给主线程添加SynchronizationContext,也就是SynchronizationContext.Current = new WinFormSynchronizationContext(),WinFormSynchronizationContext是继承SynchronizationContext并重新实现了一些方法如Send,Post,Send, Post都是通过Control.Invoke/BeginInvoke来实现与UI线程的通讯。
当然,这里的SynchronizationContext是用来做跨线程Exception处理的,Task的Exception为什么能在外面捕获到,就靠这个SynchronizationContext,这个后面详细再讲。
好了,Create函数看完,接下来看Start()函数。
先看看ExecutionContext
ExecutionContext可以认为是一个容器,里面包含了一组context,SynchronizationContext是里面其中一个,还有如SecretContext,LogicContext等,代表了线程所执行的上下文。
ExecutionContextSwitcher这个类型又是干什么的呢,看代码:
也是一个结构,主要用来做Undo操作的,也就是在执行MoveNext时如果出现异常,可以恢复原来的上下文。
接着看Start函数,RuntimeHelpers.PrepareConstrainedRegions() 就是CER(Constrained Execution Region),一般由RuntimeHelpers.PrepareConstrainedRegions() + try..catch..finally组成,用来告诉CLR这段代码很重要,不管是什么异常都不要打断,为了保证不被打断, CER内(catch和finally块)的代码不能在堆上有操作,并且预先编译好CER内的代码,一切都是为了防止被打断。
进入try块,执行ExecutionContext.EstblishCopyOnWriteScope(ref ecs)这个函数,接着看它的代码:
原来是给ExecutionContextSwitcher的属性赋值,Capture函数是抓取当前线程的ExecutionContext,这样ExecutionContextSwitcher里的Context就可以保存下来以便异常时恢复了。
继续Start函数,最重要的stateMachine.MoveNext()来了,上面一大堆都是为了这个家伙的安全执行。
整个Start看完,目的也就是执行MoveNext,那我们看看状态机里MoveNext干了些什么:
看看我们上面翻译的结果:
可以把原始代码看成三段,如图:
第一次进来由于state是-1,所以先执行第一段,接着是第二段,把state置为0并且拿到awaiter做Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this)操作,这个操作里面会在取到数据后再次MoveNext,因为state为0,所以就走到第三段,整个过程是这样。
我们详细看看Builder.AwaitUnsafeOnCompleted这个操作是怎么调用第二次MoveNext的。
一点一点看,先调用了m_coreState.GetCompletionAction,m_coreState是AsyncMethodBuilderCore类型,来看看它的实现:
这段代码看起来比较简单,主要是针对MoveNextRunner实例,传递上下文和状态机给它,大家应该可以猜到MoveNext就是用这个MoveNextRunner.Run去实现了,这个函数返回的就是MoveNextRunner.Run。
再回头看上面的代码,如果m_coreState.m_stateMachine == null,也就是第一次进来就先做PostBoxInitialization操作,看看PostBoxInitialization:
这个函数的目的有两个,一个是给状态机装箱保存下来,另一个是给runner的状态机赋值。
再看回上面的AwaitUnsafeOnCompleted函数,到awaiter.UnsafeOnCompleted(continuation)了,这个算是核心,主要就是等这个回来再调用continuation,continuation我们知道是MoveNextRunner的Run函数,先看看这个Run函数:
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,追根到底看看它是怎么实现的:
continueOnCapturedContext这个是由Task.ConfigureAwait(continueOnCapturedContext)来控制的,true则表示执行完task后转到SynchronizationContext所在的线程上去执行await后面的部分,比如说更新UI就必须在UI线程上,这个就需要设为true,如果不是要更新UI,而是还有很多的数据需要本地计算,则最好设为false,这时会在task执行完成后在线程池中拿出一个空闲的工作线程来做await后面的事,当然在Asp.net里要注意HttpContext.Current可能在false时会为Null,操作时需要注意。接着看OnCompletedInternal的代码:
主要是调用SetContinuationForAwait:
最主要看是怎么Run的,先看第一种,continueOnCapturedContext为true的:
看看PostAction:
来看看第二种:continueOnCapturedContext为false:
所以为false时就没SynchronizationContext什么事,线程池里拿个空闲线程出来运行就好了。上面有很大篇幅讲了awaiter.AwaitUnsafeOnCompleted的运行原理,因为async/await是配合awaitable用的,所以就一起分析。
那现在这个简单的async/await例子就分析完了,可能有人会觉得状态机貌似没什么用,用if/else也能轻松做到这个,没必要用MoveNext。那是因为这里只有一个await,如果更多呢,if/else就很难控制,MoveNext就只需要关注状态变化就好了。写个有三个await的函数来看看:
因为IL上面已经讲过,多个await的指令其实差不多,所以用另一种简单的方法:ILSpy来直接看翻译结果,需要在Options里把Decompile async method(async/await)关掉,如图:
MoveNext的代码:
还是比较容易理解,思路和单个await一样,这里通过goto的方式来控制流程,很聪明的做法,这样既可以跳转,又不影响taskAwaiter.IsCompleted为true时的直接运行。
在讲AsyncVoidMethodBuilder.Create时讲到SynchronizationContext的用处是处理异常,那现在来看看AsyncVoidMethodBuilder的异常处理:
看到了吧,把异常通过targetContext.Post的方式给到最开始的线程,这也是为什么在Task外面的try..catch能抓到异步异常的原因。
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块中轻松捕获。
先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的写异步操作代码,它本身并不会去创建一个新线程,线程的工作还是由Task来做,async/await只是让开发人员以直观的方式写异步操作代码,而不像以前那样到处都是callback或事件。
async/await IL翻译
先写个简单的例子:using System; using System.Threading.Tasks; namespace ILLearn { class Program { static void Main(string[] args) { DisplayDataAsync(); Console.ReadLine(); } static async void DisplayDataAsync() { Console.WriteLine("start"); var data = await GetData(); Console.WriteLine(data); Console.WriteLine("end"); } static async Task<string> GetData() { await Task.Run(async () => await Task.Delay(1000)); return "data"; } } }
编译: 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个接口函数,我们整理一下状态机代码:
struct GetDataAsyncStateMachine : IAsyncStateMachine { public int State; public AsyncVoidMethodBuilder Builder; private TaskAwaiter<string> _taskAwaiter; void IAsyncStateMachine.MoveNext(); void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine); }
这样就好看多了。
再来看看我们写的DisplayDataAsync的IL:
双击
.method private hidebysig static void DisplayDataAsync() cil managed { .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 61 6D 2B 3C 44 69 73 70 6C 61 79 44 61 74 61 41 // am+<DisplayDataA 73 79 6E 63 3E 64 5F 5F 31 00 00 ) // sync>d__1.. // 代码大小 37 (0x25) .maxstack 2 .locals init (valuetype ILLearn.Program/'<DisplayDataAsync>d__1' V_0, //这里还是局部变量,第1个是valuetype也就是值类型<DisplayDataAsync>d__1,在上面知道这是一个状态机 DisplayDataAsyncStateMachine valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder V_1) //第2个局部变量也是值类型,叫AsyncVoidMethodBuilder,在System.Runtime.CompilerServices命名空间下 IL_0000: ldloca.s V_0 //加载第1个局部变量的地址,因为是结构,在栈上,通过地址来调用函数 IL_0002: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create() //调用AsyncVoidMethodBuilder的create函数,用的是call,并且没有实例,所以create()是个静态函数 IL_0007: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //把create()的结果存到DisplayDataAsyncStateMachine结构的Builder字段 IL_000c: ldloca.s V_0 //加载第1个局部变量的地址,还是为了给这个结构的变量赋值 IL_000e: ldc.i4.m1 //加载整数 -1,上篇没有说,这个m表示minus,也就是负号 IL_000f: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //把-1存到DisplayDataAsyncStateMachine的State字段 IL_0014: ldloc.0 //加载第1个局部变量 IL_0015: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //获取第1个局部变量的Builder字段,也就是上面create()出来的 IL_001a: stloc.1 //存到第2个局部变量中 V_1 = DisplayDataAsyncStateMachine.Builder IL_001b: ldloca.s V_1 //加载第1个局部变量地址 IL_001d: ldloca.s V_0 //加载第2个局部变量地址 IL_001f: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<valuetype ILLearn.Program/'<DisplayDataAsync>d__1'>(!!0&) //调用V_0的start方法,方法有个参数!!0&,这看上去有点奇怪,指的是上面加载的V_1的地址 IL_0024: ret //返回 } // end of method Program::DisplayDataAsync
好了,这个函数的意思差不多搞懂了,我们先把它翻译成容易看懂的C#代码,大概是这个样子:
public void DisplayDataAsync() { DisplayDataAsyncStateMachine stateMachine; stateMachine.Builder = AsyncVoidMethodBuilder.Create(); stateMachine.State = -1; AsyncVoidMethodBuilder builder = stateMachine.Builder; builder.Start(ref stateMachine); }
与源代码完全不一样。
GetDataAsyncStateMachine还有两个接口函数的IL需要看下,接下来先看看这两个函数SetStateMachine和MoveNext的IL代码,把它也翻译过来,注意:IL里用的<DisplayDataAsync>d_1,<>1_state,<>_builder,<>u_1都可以用GetDataAsyncStateMachine,State, Builder,_taskAwaiter来表示了,这样更容易理解一些。
MoveNext:
.method private hidebysig newslot virtual final instance void MoveNext() cil managed { .override [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext // 代码大小 175 (0xaf) .maxstack 3 .locals init (int32 V_0, valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> V_1, class [mscorlib]System.Exception V_2) //3个局部变量 IL_0000: ldarg.0 //加载第0个参数,也就是本身 IL_0001: ldfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //加载字段State IL_0006: stloc.0 //存到第1个局部变量中,也就是V_0 = State .try //try 块 { IL_0007: ldloc.0 //加载第1个局部变量 IL_0008: brfalse.s IL_0048 //是false也就是 V_0 == 0则跳转到IL_0048 IL_000a: ldstr "start" //加载string "start" IL_000f: call void [mscorlib]System.Console::WriteLine(string) //调用Console.WriteLine("start") IL_0014: call class [mscorlib]System.Threading.Tasks.Task`1<string> ILLearn.Program::GetData() //调用静态方法Program.GetData() IL_0019: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<string>::GetAwaiter() //调用GetData()返回Task的GetAwaiter()方法 IL_001e: stloc.1 //把GetAwaiter()的结果存到第2个局部变量中也就是V_1 = GetData().GetAwaiter() IL_001f: ldloca.s V_1 //加载第2个局部变量V_1的地址 IL_0021: call instance bool valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>::get_IsCompleted() //调用实例属性 IsCompleted IL_0026: brtrue.s IL_0064 //如果V_1.IsCompleted == true则跳转到IL_0064 IL_0028: ldarg.0 //加载this IL_0029: ldc.i4.0 //加载整数0 IL_002a: dup //复制, 因为要存两份 IL_002b: stloc.0 //存到第1个局部变量中,V_0=0 IL_002c: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //存到State,State=0 IL_0031: ldarg.0 //加载this IL_0032: ldloc.1 //加载第2个局部变量 IL_0033: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1' //存到<>u__1也就是_taskAwaiter中,_taskAwaiter = V_1 IL_0038: ldarg.0 //加载this IL_0039: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //加载Builder的地址 IL_003e: ldloca.s V_1 //加载V_1的地址 IL_0040: ldarg.0 //加载this 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,都是引用 IL_0046: leave.s IL_00ae // 跳到IL_00ae,也就是return IL_0048: ldarg.0 //从IL_0008跳过来,加载this IL_0049: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1' //加载_taskAwaiter IL_004e: stloc.1 //存到第2个局部变量,V_1 = _taskAwaiter IL_004f: ldarg.0 //加载this IL_0050: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1' //加载_taskAwaiter地址 IL_0055: initobj valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> //初始化结构,也就是_taskAwaiter = default(TaskAwaiter<string>) IL_005b: ldarg.0 //加载this IL_005c: ldc.i4.m1 //加载-1 IL_005d: dup //复制 IL_005e: stloc.0 //把-1存到V_0中,V_0 = -1 IL_005f: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //存到State,State=-1 IL_0064: ldloca.s V_1 //从IL_0026跳过来的,加载V_1的地址 IL_0066: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>::GetResult() //调用V_1.GetResult() IL_006b: ldloca.s V_1 //加载V_1的地址 IL_006d: initobj valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> //初始化结构,也就是V_1 = default(TaskAwaiter<string>) IL_0073: call void [mscorlib]System.Console::WriteLine(string) // Console.WriteLine 写GetResult返回的值 IL_0078: ldstr "end" IL_007d: call void [mscorlib]System.Console::WriteLine(string) //Console.WriteLine("end") IL_0082: leave.s IL_009b //没异常,跳到IL_009b } // end .try catch [mscorlib]System.Exception //catch 块 { IL_0084: stloc.2 //把异常存到V_2 IL_0085: ldarg.0 //加载this IL_0086: ldc.i4.s -2 //加载-2 IL_0088: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //State = -2 IL_008d: ldarg.0 //加载this IL_008e: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //加载Builder的地址 IL_0093: ldloc.2 //加载第3个局部变量Exception IL_0094: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetException(class [mscorlib]System.Exception) //调用Builder.SetException,参数就是第3个局部变量 IL_0099: leave.s IL_00ae //return } // end handler IL_009b: ldarg.0 //加载this IL_009c: ldc.i4.s -2 //加载-2 IL_009e: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //State = -2 IL_00a3: ldarg.0 //加载this IL_00a4: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder'//加载Builder的地址 IL_00a9: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetResult() //Builder.SetResult() IL_00ae: ret //return } // end of method '<DisplayDataAsync>d__1'::MoveNext 翻译整理一下: V_0用state表示, V_1用awaiter表示,V_2用ex表示 void IAsyncStateMachine.MoveNext() { int state = State; try { TaskAwaiter<string> awaiter; if (state != 0) // 状态不是0就进来,默认是-1 { Console.WriteLine("start"); // 执行 await 之前的部分 awaiter = Program.GetData().GetAwaiter(); // 获取 awaiter if (!awaiter.IsCompleted) //判断是否完成,完成的话就不用分开了,直接执行后面的 { state = 0; State = 0; // 把状态变为0, awaiter执行完成后就不用进这里了 _taskAwaiter = awaiter; // 保存awaiter, awaiter回来后要靠_taskAwaiter来取结果 Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); // 这里面主要是构造一个action - MoveNextRunner,用来在awaiter.complete事件触发后走到这个状态机的MoveNext(),上面把state变了0了,再走这个函数的话就可以走到await后面的部分,后面再详细讲 return; // 返回 } } else { awaiter = _taskAwaiter; state = -1; State = -1; } var result = awaiter.GetResult(); //awaiter回来后取得结果 Console.WriteLine(result); // 走 await 后面的部分 Console.WriteLine("end"); } catch(Exception ex) { State = -2; Builder.SetException(ex); } State = -2; Builder.SetResult(); }
SetStateMachine:
.method private hidebysig newslot virtual final instance void SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) cil managed { .custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 ) .override [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine // 代码大小 13 (0xd) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' IL_0006: ldarg.1 IL_0007: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine) IL_000c: ret } // end of method '<DisplayDataAsync>d__1'::SetStateMachine 这个很简单,就不一一写了,直接翻译: void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { Builder.SetStateMachine(stateMachine); }
因为是照着IL直译,代码可能有点冗余,不过不伤大雅。
async/await原理
现在疏理一下,从DisplayDataAsync开始,先是创建一个状态机,把状态变量State初始化为-1,Builder使用AsyncVoidMethodBuilder.Create来创建,既而调用这个builder的Start函数并把状态机的引用传过去。那重点就是这个AsyncVoidMethodBuilder的作用,AsyncVoidMethodBuilder在命名空间System.Runtime.CompilerServices下,我们来读一下它的源码,.net的BCL已经开源了,所以直接去github上找就行了。
这文件里面有这么几个重要类AsyncVoidMethodBuilder,AsyncTaskMethodBuilder,AsyncTaskMethodBuilder<T>,AsyncMethodBuilderCore及AsyncMethodBuilderCore内的MoveNextRunner。
首先为什么DsiplayDataAsync用到的是AsyncVoidMethodBuilder,因为DisplayDataAsync返回的是void,在ildasm里双击GetData你会发现如下IL:
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()函数:
public static AsyncVoidMethodBuilder Create() { SynchronizationContext sc = SynchronizationContext.CurrentNoFlow; if (sc != null) sc.OperationStarted(); return new AsyncVoidMethodBuilder() { m_synchronizationContext = sc }; }
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()函数。
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { if (stateMachine == null) throw new ArgumentNullException("stateMachine"); Contract.EndContractBlock(); ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher); RuntimeHelpers.PrepareConstrainedRegions(); try { ExecutionContext.EstablishCopyOnWriteScope(ref ecs); stateMachine.MoveNext(); } finally { ecs.Undo(); } }
Contract.EndContractBlock();这个是一个契约标记,一般用在throw后面,没功能性的作用,这里不多讲,有兴趣的可以去翻下契约式编程。
先看看ExecutionContext
ExecutionContext可以认为是一个容器,里面包含了一组context,SynchronizationContext是里面其中一个,还有如SecretContext,LogicContext等,代表了线程所执行的上下文。
ExecutionContextSwitcher这个类型又是干什么的呢,看代码:
internal struct ExecutionContextSwitcher { internal ExecutionContext m_ec; internal SynchronizationContext m_sc; internal void Undo() { SynchronizationContext.SetSynchronizationContext(m_sc); ExecutionContext.Restore(m_ec); } }
也是一个结构,主要用来做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得不到执行,对象也不能被释放,从而造成资源泄漏。
GC如果是因为内存不足而触发,而这时Finalize如果没有预编译,就有可能发生没有内存可供Finalize编译,Finalize得不到执行,对象也不能被释放,从而造成资源泄漏。
进入try块,执行ExecutionContext.EstblishCopyOnWriteScope(ref ecs)这个函数,接着看它的代码:
static internal void EstablishCopyOnWriteScope(ref ExecutionContextSwitcher ecsw) { ecsw.m_ec = Capture(); ecsw.m_sc = SynchronizationContext.CurrentNoFlow; }
原来是给ExecutionContextSwitcher的属性赋值,Capture函数是抓取当前线程的ExecutionContext,这样ExecutionContextSwitcher里的Context就可以保存下来以便异常时恢复了。
继续Start函数,最重要的stateMachine.MoveNext()来了,上面一大堆都是为了这个家伙的安全执行。
整个Start看完,目的也就是执行MoveNext,那我们看看状态机里MoveNext干了些什么:
看看我们上面翻译的结果:
void IAsyncStateMachine.MoveNext() { int state = State; try { TaskAwaiter<string> awaiter; if (state != 0) // 状态不是0就进来,默认是-1 { Console.WriteLine("start"); // 执行 await 之前的部分 awaiter = Program.GetData().GetAwaiter(); // 获取 awaiter if (!awaiter.IsCompleted) //判断是否完成,完成的话就不用分开了,直接执行后面的 { state = 0; State = 0; // 把状态变为0, awaiter执行完成后再次MoveNext就不用进这里了 _taskAwaiter = awaiter; // 保存awaiter, awaiter回来后要靠_taskAwaiter来取结果 Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); // 这里面主要是构造一个action - MoveNextRunner,用来在awaiter.complete事件触发后继续走这个状态机的MoveNext(),上面把state变了0了,再走这个函数的话就可以走到await后面的部分,下面再详细讲 return; // 返回 } } else { awaiter = _taskAwaiter; state = -1; State = -1; } var result = awaiter.GetResult(); //awaiter回来后取得结果 Console.WriteLine(result); // 走 await 后面的部分 Console.WriteLine("end"); } catch (Exception ex) { State = -2; Builder.SetException(ex); } State = -2; Builder.SetResult(); }
可以把原始代码看成三段,如图:
第一次进来由于state是-1,所以先执行第一段,接着是第二段,把state置为0并且拿到awaiter做Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this)操作,这个操作里面会在取到数据后再次MoveNext,因为state为0,所以就走到第三段,整个过程是这样。
我们详细看看Builder.AwaitUnsafeOnCompleted这个操作是怎么调用第二次MoveNext的。
public void AwaitOnCompleted<TAwaiter, TStateMachine>( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { try { AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null; var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize); Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action."); // If this is our first await, such that we've not yet boxed the state machine, do so now. if (m_coreState.m_stateMachine == null) { if (AsyncCausalityTracer.LoggingOn) AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0); m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null); } awaiter.OnCompleted(continuation); } catch (Exception exc) { AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null); } }
一点一点看,先调用了m_coreState.GetCompletionAction,m_coreState是AsyncMethodBuilderCore类型,来看看它的实现:
internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize) { Contract.Assert(m_defaultContextAction == null || m_stateMachine != null, "Expected non-null m_stateMachine on non-null m_defaultContextAction"); Debugger.NotifyOfCrossThreadDependency(); var capturedContext = ExecutionContext.FastCapture(); //获取当前线程的ExecutionContext Action action; MoveNextRunner runner; if (capturedContext != null && capturedContext.IsPreAllocatedDefault) { action = m_defaultContextAction; if (action != null) { Contract.Assert(m_stateMachine != null, "If the delegate was set, the state machine should have been as well."); return action; } runner = new MoveNextRunner(capturedContext, m_stateMachine); //new一个MoveNextRunner实例,并把ExecutionContext和状态机传过去 action = new Action(runner.Run); //runner.Run的action if (taskForTracing != null) { m_defaultContextAction = action = OutputAsyncCausalityEvents(taskForTracing, action); } else { m_defaultContextAction = action; } } else { runner = new MoveNextRunner(capturedContext, m_stateMachine); action = new Action(runner.Run); if (taskForTracing != null) { action = OutputAsyncCausalityEvents(taskForTracing, action); } } if (m_stateMachine == null) runnerToInitialize = runner; return action; }
这段代码看起来比较简单,主要是针对MoveNextRunner实例,传递上下文和状态机给它,大家应该可以猜到MoveNext就是用这个MoveNextRunner.Run去实现了,这个函数返回的就是MoveNextRunner.Run。
再回头看上面的代码,如果m_coreState.m_stateMachine == null,也就是第一次进来就先做PostBoxInitialization操作,看看PostBoxInitialization:
internal void PostBoxInitialization(IAsyncStateMachine stateMachine, MoveNextRunner runner, Task builtTask) { if (builtTask != null) { if (AsyncCausalityTracer.LoggingOn) AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, builtTask.Id, "Async: " + stateMachine.GetType().Name, 0); if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled) System.Threading.Tasks.Task.AddToActiveTasks(builtTask); } m_stateMachine = stateMachine; //给m_stateMachine赋值,因为m_stateMachine是internal IAsyncStateMachine m_stateMachine;这样定义的,所以把struct stateMachine传给这个接口类型时会装箱,目的是在Builder里面保存这个状态机,下次不会走这了 m_stateMachine.SetStateMachine(m_stateMachine); Contract.Assert(runner.m_stateMachine == null, "The runner's state machine should not yet have been populated."); Contract.Assert(m_stateMachine != null, "The builder's state machine field should have been initialized."); runner.m_stateMachine = m_stateMachine; }
这个函数的目的有两个,一个是给状态机装箱保存下来,另一个是给runner的状态机赋值。
再看回上面的AwaitUnsafeOnCompleted函数,到awaiter.UnsafeOnCompleted(continuation)了,这个算是核心,主要就是等这个回来再调用continuation,continuation我们知道是MoveNextRunner的Run函数,先看看这个Run函数:
internal void Run() { Contract.Assert(m_stateMachine != null, "The state machine must have been set before calling Run."); if (m_context != null) { try { ContextCallback callback = s_invokeMoveNext; if (callback == null) { s_invokeMoveNext = callback = InvokeMoveNext; } ExecutionContext.Run(m_context, callback, m_stateMachine, preserveSyncCtx: true); //主要就是用ExecutionContext应用到当前线程来执行这个((IAsyncStateMachine)stateMachine).MoveNext() } finally { m_context.Dispose(); } } else { m_stateMachine.MoveNext(); } } private static ContextCallback s_invokeMoveNext; private static void InvokeMoveNext(object stateMachine) { ((IAsyncStateMachine)stateMachine).MoveNext(); }
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,追根到底看看它是怎么实现的:
public void UnsafeOnCompleted(Action continuation) { OnCompletedInternal(m_task, continuation, continueOnCapturedContext: true, flowExecutionContext: false); }
continueOnCapturedContext这个是由Task.ConfigureAwait(continueOnCapturedContext)来控制的,true则表示执行完task后转到SynchronizationContext所在的线程上去执行await后面的部分,比如说更新UI就必须在UI线程上,这个就需要设为true,如果不是要更新UI,而是还有很多的数据需要本地计算,则最好设为false,这时会在task执行完成后在线程池中拿出一个空闲的工作线程来做await后面的事,当然在Asp.net里要注意HttpContext.Current可能在false时会为Null,操作时需要注意。接着看OnCompletedInternal的代码:
internal static void OnCompletedInternal(Task task, Action continuation, bool continueOnCapturedContext, bool flowExecutionContext) { if (continuation == null) throw new ArgumentNullException("continuation"); StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; if (TplEtwProvider.Log.IsEnabled() || Task.s_asyncDebuggingEnabled) { continuation = OutputWaitEtwEvents(task, continuation); } task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext, ref stackMark); }
主要是调用SetContinuationForAwait:
internal void SetContinuationForAwait( Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark) { Contract.Requires(continuationAction != null); TaskContinuation tc = null; if (continueOnCapturedContext) //如果需要用到SynchronizationContext { var syncCtx = SynchronizationContext.CurrentNoFlow; //获取当前SynchronizationContext if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext)) //当前SynchronizationContext和传进来的SynchronizationContext不相等 { tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction, flowExecutionContext, ref stackMark); //用SynchronizationContext来转到目标线程去执行 } Else { var scheduler = TaskScheduler.InternalCurrent; if (scheduler != null && scheduler != TaskScheduler.Default) { tc = new TaskSchedulerAwaitTaskContinuation(scheduler, continuationAction, flowExecutionContext, ref stackMark); } } } if (tc == null && flowExecutionContext) { tc = new AwaitTaskContinuation(continuationAction, flowExecutionContext: true, stackMark: ref stackMark); // continueOnCapturedContext = false时 } if (tc != null) { if (!AddTaskContinuation(tc, addBeforeOthers: false)) tc.Run(this, bCanInlineContinuationTask: false); //开始执行Run } else { Contract.Assert(!flowExecutionContext, "We already determined we're not required to flow context."); if (!AddTaskContinuation(continuationAction, addBeforeOthers: false)) AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this); } }
最主要看是怎么Run的,先看第一种,continueOnCapturedContext为true的:
internal sealed override void Run(Task task, bool canInlineContinuationTask) { if (canInlineContinuationTask && this.m_syncContext == SynchronizationContext.CurrentNoFlow) //如果当前线程的SynchronizationContext和syncContext一样,那表示就是一个线程,直接执行就好了 { base.RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), this.m_action, ref Task.t_currentTask); return; } TplEtwProvider log = TplEtwProvider.Log; if (log.IsEnabled()) { this.m_continuationId = Task.NewId(); log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, this.m_continuationId); } base.RunCallback(SynchronizationContextAwaitTaskContinuation.GetPostActionCallback(), this, ref Task.t_currentTask); // 这里用到了GetPostActionCallback()来执行 }
看看PostAction:
private static void PostAction(object state) { SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = (SynchronizationContextAwaitTaskContinuation)state; if (TplEtwProvider.Log.TasksSetActivityIds && synchronizationContextAwaitTaskContinuation.m_continuationId != 0) { 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线程执行 return; } synchronizationContextAwaitTaskContinuation.m_syncContext.Post(SynchronizationContextAwaitTaskContinuation.s_postCallback, synchronizationContextAwaitTaskContinuation.m_action); }
来看看第二种:continueOnCapturedContext为false:
internal override void Run(Task task, bool canInlineContinuationTask) { if (canInlineContinuationTask && AwaitTaskContinuation.IsValidLocationForInlining) { this.RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), this.m_action, ref Task.t_currentTask); //这里去到RunCallback return; } TplEtwProvider log = TplEtwProvider.Log; if (log.IsEnabled()) { this.m_continuationId = Task.NewId(); log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, this.m_continuationId); } ThreadPool.UnsafeQueueCustomWorkItem(this, false); // 这也是通过线程池去运行 } protected void RunCallback(ContextCallback callback, object state, ref Task currentTask) { Task task = currentTask; try { if (task != null) { currentTask = null; } if (this.m_capturedContext == null) { callback(state); } else { ExecutionContext.Run(this.m_capturedContext, callback, state, true); //就是通过ExecutionContext.Run去运行 } } catch (Exception arg_2A_0) { AwaitTaskContinuation.ThrowAsyncIfNecessary(arg_2A_0); } finally { if (task != null) { currentTask = task; } if (this.m_capturedContext != null) { this.m_capturedContext.Dispose(); } } }
所以为false时就没SynchronizationContext什么事,线程池里拿个空闲线程出来运行就好了。上面有很大篇幅讲了awaiter.AwaitUnsafeOnCompleted的运行原理,因为async/await是配合awaitable用的,所以就一起分析。
那现在这个简单的async/await例子就分析完了,可能有人会觉得状态机貌似没什么用,用if/else也能轻松做到这个,没必要用MoveNext。那是因为这里只有一个await,如果更多呢,if/else就很难控制,MoveNext就只需要关注状态变化就好了。写个有三个await的函数来看看:
static async void DisplayDataAsync() { Console.WriteLine("start"); Console.WriteLine("progress_1"); await GetData(); Console.WriteLine("progress_2"); await GetData(); Console.WriteLine("progress_3"); await GetData(); Console.WriteLine("end"); }
因为IL上面已经讲过,多个await的指令其实差不多,所以用另一种简单的方法:ILSpy来直接看翻译结果,需要在Options里把Decompile async method(async/await)关掉,如图:
MoveNext的代码:
void IAsyncStateMachine.MoveNext() { int num = this.<> 1__state; try { TaskAwaiter<string> taskAwaiter; switch (num) { case 0: taskAwaiter = this.<> u__1; this.<> u__1 = default(TaskAwaiter<string>); this.<> 1__state = -1; break; case 1: taskAwaiter = this.<> u__1; this.<> u__1 = default(TaskAwaiter<string>); this.<> 1__state = -1; goto IL_ED; case 2: taskAwaiter = this.<> u__1; this.<> u__1 = default(TaskAwaiter<string>); this.<> 1__state = -1; goto IL_157; default: Console.WriteLine("start"); Console.WriteLine("progress_1"); taskAwaiter = Program.GetData().GetAwaiter(); if (!taskAwaiter.IsCompleted) { this.<> 1__state = 0; this.<> u__1 = taskAwaiter; this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this); return; } break; } taskAwaiter.GetResult(); taskAwaiter = default(TaskAwaiter<string>); Console.WriteLine("progress_2"); taskAwaiter = Program.GetData().GetAwaiter(); if (!taskAwaiter.IsCompleted) { this.<> 1__state = 1; this.<> u__1 = taskAwaiter; this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this); return; } IL_ED: taskAwaiter.GetResult(); taskAwaiter = default(TaskAwaiter<string>); Console.WriteLine("progress_3"); taskAwaiter = Program.GetData().GetAwaiter(); if (!taskAwaiter.IsCompleted) { this.<> 1__state = 2; this.<> u__1 = taskAwaiter; this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this); return; } IL_157: taskAwaiter.GetResult(); taskAwaiter = default(TaskAwaiter<string>); Console.WriteLine("end"); } catch (Exception exception) { this.<> 1__state = -2; this.<> t__builder.SetException(exception); return; } this.<> 1__state = -2; this.<> t__builder.SetResult(); }
还是比较容易理解,思路和单个await一样,这里通过goto的方式来控制流程,很聪明的做法,这样既可以跳转,又不影响taskAwaiter.IsCompleted为true时的直接运行。
在讲AsyncVoidMethodBuilder.Create时讲到SynchronizationContext的用处是处理异常,那现在来看看AsyncVoidMethodBuilder的异常处理:
internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext) { var edi = ExceptionDispatchInfo.Capture(exception); if (targetContext != null) { try { targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi); return; } catch (Exception postException) { edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException)); } } }
看到了吧,把异常通过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块中轻松捕获。
相关文章推荐
- document.domain 跨域问题
- 我与小娜(25):聚焦人工智能挑战人类智慧的一场赛事
- spark , NoSuchMethodError: ConcurrentHashMap.keySet 和failed to connect to master的错
- HDU 2476 String painter(区间DP)
- HDU 2476 String painter(区间DP)
- ‘asm’ operand has impossible constraints
- DNS -- Domain Name System 简介
- WAI-ARIA roles
- ZBrush中的PaintStop插件该怎么灵活运用
- linker command failed with exit code 1 (use -v to see 错误总结
- PAT (Advanced Level) Practise 1014 Waiting in Line (30)
- computer repair services in Hangzhou
- Aidl与信使的区别
- 3 AMQP 0-9-1 Model Explained
- Socket连接时Input.available()报空指针错误
- 1086. Tree Traversals Again (25)
- POJ 3007 Organize Your Train part II map与字典树的比较
- 什么是信息增益(Information Gain)
- Unable to simultaneously satisfy constraints.
- HDOJ 1151 Air Raid(二分图最小路径覆盖)