接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理。

先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的写异步操作代码,它本身并不会去创建一个新线程,线程的工作还是由Task来做,async/await只是让开发人员以直观的方式写异步操作代码,而不像以前那样到处都是callback或事件。

async/await IL翻译

先写个简单的例子:

 1 using System;
2 using System.Threading.Tasks;
3
4 namespace ILLearn
5 {
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 DisplayDataAsync();
11
12 Console.ReadLine();
13 }
14
15 static async void DisplayDataAsync()
16 {
17 Console.WriteLine("start");
18
19 var data = await GetData();
20
21 Console.WriteLine(data);
22
23 Console.WriteLine("end");
24 }
25
26 static async Task<string> GetData()
27 {
28 await Task.Run(async () => await Task.Delay(1000));
29 return "data";
30 }
31 }
32 }

编译: 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个接口函数,我们整理一下状态机代码:

 1 struct GetDataAsyncStateMachine : IAsyncStateMachine
2 {
3 public int State;
4
5 public AsyncVoidMethodBuilder Builder;
6
7 private TaskAwaiter<string> _taskAwaiter;
8
9 void IAsyncStateMachine.MoveNext();
10
11 void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine);
12 }

这样就好看多了。

再来看看我们写的DisplayDataAsync的IL:

双击

 1 .method private hidebysig static void  DisplayDataAsync() cil managed
2 {
3 .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
4 61 6D 2B 3C 44 69 73 70 6C 61 79 44 61 74 61 41 // am+<DisplayDataA
5 73 79 6E 63 3E 64 5F 5F 31 00 00 ) // sync>d__1..
6 // 代码大小 37 (0x25)
7 .maxstack 2
8 .locals init (valuetype ILLearn.Program/'<DisplayDataAsync>d__1' V_0, //这里还是局部变量,第1个是valuetype也就是值类型<DisplayDataAsync>d__1,在上面知道这是一个状态机 DisplayDataAsyncStateMachine
9 valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder V_1) //第2个局部变量也是值类型,叫AsyncVoidMethodBuilder,在System.Runtime.CompilerServices命名空间下
10 IL_0000: ldloca.s V_0 //加载第1个局部变量的地址,因为是结构,在栈上,通过地址来调用函数
11 IL_0002: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create() //调用AsyncVoidMethodBuilder的create函数,用的是call,并且没有实例,所以create()是个静态函数
12 IL_0007: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //把create()的结果存到DisplayDataAsyncStateMachine结构的Builder字段
13 IL_000c: ldloca.s V_0 //加载第1个局部变量的地址,还是为了给这个结构的变量赋值
14 IL_000e: ldc.i4.m1 //加载整数 -1,上篇没有说,这个m表示minus,也就是负号
15 IL_000f: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //把-1存到DisplayDataAsyncStateMachine的State字段
16 IL_0014: ldloc.0 //加载第1个局部变量
17 IL_0015: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //获取第1个局部变量的Builder字段,也就是上面create()出来的
18 IL_001a: stloc.1 //存到第2个局部变量中 V_1 = DisplayDataAsyncStateMachine.Builder
19 IL_001b: ldloca.s V_1 //加载第1个局部变量地址
20 IL_001d: ldloca.s V_0 //加载第2个局部变量地址
21 IL_001f: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<valuetype ILLearn.Program/'<DisplayDataAsync>d__1'>(!!0&) //调用V_0的start方法,方法有个参数!!0&,这看上去有点奇怪,指的是上面加载的V_1的地址
22 IL_0024: ret //返回
23 } // end of method Program::DisplayDataAsync

好了,这个函数的意思差不多搞懂了,我们先把它翻译成容易看懂的C#代码,大概是这个样子:

 1 public void DisplayDataAsync()
2 {
3 DisplayDataAsyncStateMachine stateMachine;
4
5 stateMachine.Builder = AsyncVoidMethodBuilder.Create();
6
7 stateMachine.State = -1;
8
9 AsyncVoidMethodBuilder builder = stateMachine.Builder;
10
11 builder.Start(ref stateMachine);
12 }

与源代码完全不一样。

GetDataAsyncStateMachine还有两个接口函数的IL需要看下,接下来先看看这两个函数SetStateMachine和MoveNext的IL代码,把它也翻译过来,注意:IL里用的<DisplayDataAsync>d_1,<>1_state,<>_builder,<>u_1都可以用GetDataAsyncStateMachine,State, Builder,_taskAwaiter来表示了,这样更容易理解一些。

MoveNext:

  1 .method private hidebysig newslot virtual final
2 instance void MoveNext() cil managed
3 {
4 .override [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext
5 // 代码大小 175 (0xaf)
6 .maxstack 3
7 .locals init (int32 V_0,
8 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> V_1,
9 class [mscorlib]System.Exception V_2) //3个局部变量
10 IL_0000: ldarg.0 //加载第0个参数,也就是本身
11 IL_0001: ldfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //加载字段State
12 IL_0006: stloc.0 //存到第1个局部变量中,也就是V_0 = State
13 .try //try 块
14 {
15 IL_0007: ldloc.0 //加载第1个局部变量
16 IL_0008: brfalse.s IL_0048 //是false也就是 V_0 == 0则跳转到IL_0048
17 IL_000a: ldstr "start" //加载string "start"
18 IL_000f: call void [mscorlib]System.Console::WriteLine(string) //调用Console.WriteLine("start")
19 IL_0014: call class [mscorlib]System.Threading.Tasks.Task`1<string> ILLearn.Program::GetData() //调用静态方法Program.GetData()
20 IL_0019: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<string>::GetAwaiter() //调用GetData()返回Task的GetAwaiter()方法
21 IL_001e: stloc.1 //把GetAwaiter()的结果存到第2个局部变量中也就是V_1 = GetData().GetAwaiter()
22 IL_001f: ldloca.s V_1 //加载第2个局部变量V_1的地址
23 IL_0021: call instance bool valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>::get_IsCompleted() //调用实例属性 IsCompleted
24 IL_0026: brtrue.s IL_0064 //如果V_1.IsCompleted == true则跳转到IL_0064
25 IL_0028: ldarg.0 //加载this
26 IL_0029: ldc.i4.0 //加载整数0
27 IL_002a: dup //复制, 因为要存两份
28 IL_002b: stloc.0 //存到第1个局部变量中,V_0=0
29 IL_002c: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //存到State,State=0
30 IL_0031: ldarg.0 //加载this
31 IL_0032: ldloc.1 //加载第2个局部变量
32 IL_0033: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1' //存到<>u__1也就是_taskAwaiter中,_taskAwaiter = V_1
33 IL_0038: ldarg.0 //加载this
34 IL_0039: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //加载Builder的地址
35 IL_003e: ldloca.s V_1 //加载V_1的地址
36 IL_0040: ldarg.0 //加载this
37 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,都是引用
38 IL_0046: leave.s IL_00ae // 跳到IL_00ae,也就是return
39 IL_0048: ldarg.0 //从IL_0008跳过来,加载this
40 IL_0049: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1' //加载_taskAwaiter
41 IL_004e: stloc.1 //存到第2个局部变量,V_1 = _taskAwaiter
42 IL_004f: ldarg.0 //加载this
43 IL_0050: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> ILLearn.Program/'<DisplayDataAsync>d__1'::'<>u__1' //加载_taskAwaiter地址
44 IL_0055: initobj valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> //初始化结构,也就是_taskAwaiter = default(TaskAwaiter<string>)
45 IL_005b: ldarg.0 //加载this
46 IL_005c: ldc.i4.m1 //加载-1
47 IL_005d: dup //复制
48 IL_005e: stloc.0 //把-1存到V_0中,V_0 = -1
49 IL_005f: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //存到State,State=-1
50 IL_0064: ldloca.s V_1 //从IL_0026跳过来的,加载V_1的地址
51 IL_0066: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string>::GetResult() //调用V_1.GetResult()
52 IL_006b: ldloca.s V_1 //加载V_1的地址
53 IL_006d: initobj valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<string> //初始化结构,也就是V_1 = default(TaskAwaiter<string>)
54 IL_0073: call void [mscorlib]System.Console::WriteLine(string) // Console.WriteLine 写GetResult返回的值
55 IL_0078: ldstr "end"
56 IL_007d: call void [mscorlib]System.Console::WriteLine(string) //Console.WriteLine("end")
57 IL_0082: leave.s IL_009b //没异常,跳到IL_009b
58 } // end .try
59 catch [mscorlib]System.Exception //catch 块
60 {
61 IL_0084: stloc.2 //把异常存到V_2
62 IL_0085: ldarg.0 //加载this
63 IL_0086: ldc.i4.s -2 //加载-2
64 IL_0088: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //State = -2
65 IL_008d: ldarg.0 //加载this
66 IL_008e: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder' //加载Builder的地址
67 IL_0093: ldloc.2 //加载第3个局部变量Exception
68 IL_0094: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetException(class [mscorlib]System.Exception) //调用Builder.SetException,参数就是第3个局部变量
69 IL_0099: leave.s IL_00ae //return
70 } // end handler
71 IL_009b: ldarg.0 //加载this
72 IL_009c: ldc.i4.s -2 //加载-2
73 IL_009e: stfld int32 ILLearn.Program/'<DisplayDataAsync>d__1'::'<>1__state' //State = -2
74 IL_00a3: ldarg.0 //加载this
75 IL_00a4: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder'//加载Builder的地址
76 IL_00a9: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetResult() //Builder.SetResult()
77 IL_00ae: ret //return
78 } // end of method '<DisplayDataAsync>d__1'::MoveNext
79
80 翻译整理一下:
81 V_0用state表示, V_1用awaiter表示,V_2用ex表示
82
83 void IAsyncStateMachine.MoveNext()
84 {
85 int state = State;
86 try
87 {
88 TaskAwaiter<string> awaiter;
89 if (state != 0) // 状态不是0就进来,默认是-1
90 {
91 Console.WriteLine("start"); // 执行 await 之前的部分
92
93 awaiter = Program.GetData().GetAwaiter(); // 获取 awaiter
94
95 if (!awaiter.IsCompleted) //判断是否完成,完成的话就不用分开了,直接执行后面的
96 {
97 state = 0;
98 State = 0; // 把状态变为0, awaiter执行完成后就不用进这里了
99 _taskAwaiter = awaiter; // 保存awaiter, awaiter回来后要靠_taskAwaiter来取结果
100 Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); // 这里面主要是构造一个action - MoveNextRunner,用来在awaiter.complete事件触发后走到这个状态机的MoveNext(),上面把state变了0了,再走这个函数的话就可以走到await后面的部分,后面再详细讲
101 return; // 返回
102 }
103 }
104 else
105 {
106 awaiter = _taskAwaiter;
107 state = -1;
108 State = -1;
109 }
110
111 var result = awaiter.GetResult(); //awaiter回来后取得结果
112
113 Console.WriteLine(result); // 走 await 后面的部分
114
115 Console.WriteLine("end");
116 }
117 catch(Exception ex)
118 {
119 State = -2;
120 Builder.SetException(ex);
121 }
122
123 State = -2;
124 Builder.SetResult();
125 }

SetStateMachine:

 1 .method private hidebysig newslot virtual final
2 instance void SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine) cil managed
3 {
4 .custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( 01 00 00 00 )
5 .override [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine
6 // 代码大小 13 (0xd)
7 .maxstack 8
8 IL_0000: ldarg.0
9 IL_0001: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ILLearn.Program/'<DisplayDataAsync>d__1'::'<>t__builder'
10 IL_0006: ldarg.1
11 IL_0007: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
12 IL_000c: ret
13 } // end of method '<DisplayDataAsync>d__1'::SetStateMachine
14
15 这个很简单,就不一一写了,直接翻译:
16 void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
17 {
18 Builder.SetStateMachine(stateMachine);
19 }

因为是照着IL直译,代码可能有点冗余,不过不伤大雅。

async/await原理

现在疏理一下,从DisplayDataAsync开始,先是创建一个状态机,把状态变量State初始化为-1,Builder使用AsyncVoidMethodBuilder.Create来创建,既而调用这个builder的Start函数并把状态机的引用传过去。

那重点就是这个AsyncVoidMethodBuilder的作用,AsyncVoidMethodBuilder在命名空间System.Runtime.CompilerServices下,我们来读一下它的源码,.net的BCL已经开源了,所以直接去github上找就行了。

https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs

这文件里面有这么几个重要类AsyncVoidMethodBuilder,AsyncTaskMethodBuilder,AsyncTaskMethodBuilder<T>,AsyncMethodBuilderCore及AsyncMethodBuilderCore内的MoveNextRunner。

首先为什么DsiplayDataAsync用到的是AsyncVoidMethodBuilder,因为DisplayDataAsync返回的是void,在ildasm里双击GetData你会发现如下IL:

1 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()函数:

1 public static AsyncVoidMethodBuilder Create()
2 {
3 SynchronizationContext sc = SynchronizationContext.CurrentNoFlow;
4 if (sc != null)
5 sc.OperationStarted();
6 return new AsyncVoidMethodBuilder() { m_synchronizationContext = sc };
7 }

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()函数。

 1 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
2 {
3 if (stateMachine == null) throw new ArgumentNullException("stateMachine");
4 Contract.EndContractBlock();
5
6 ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher);
7 RuntimeHelpers.PrepareConstrainedRegions();
8 try
9 {
10 ExecutionContext.EstablishCopyOnWriteScope(ref ecs);
11 stateMachine.MoveNext();
12 }
13 finally
14 {
15 ecs.Undo();
16 }
17 }

Contract.EndContractBlock();这个是一个契约标记,一般用在throw后面,没功能性的作用,这里不多讲,有兴趣的可以去翻下契约式编程。

先看看ExecutionContext

https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Threading/ExecutionContext.cs

ExecutionContext可以认为是一个容器,里面包含了一组context,SynchronizationContext是里面其中一个,还有如SecretContext,LogicContext等,代表了线程所执行的上下文。

ExecutionContextSwitcher这个类型又是干什么的呢,看代码:

 1 internal struct ExecutionContextSwitcher
2 {
3 internal ExecutionContext m_ec;
4 internal SynchronizationContext m_sc;
5
6 internal void Undo()
7 {
8 SynchronizationContext.SetSynchronizationContext(m_sc);
9 ExecutionContext.Restore(m_ec);
10 }
11 }

也是一个结构,主要用来做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得不到执行,对象也不能被释放,从而造成资源泄漏。

进入try块,执行ExecutionContext.EstblishCopyOnWriteScope(ref ecs)这个函数,接着看它的代码:

1 static internal void EstablishCopyOnWriteScope(ref ExecutionContextSwitcher ecsw)
2 {
3 ecsw.m_ec = Capture();
4 ecsw.m_sc = SynchronizationContext.CurrentNoFlow;
5 }

原来是给ExecutionContextSwitcher的属性赋值,Capture函数是抓取当前线程的ExecutionContext,这样ExecutionContextSwitcher里的Context就可以保存下来以便异常时恢复了。

继续Start函数,最重要的stateMachine.MoveNext()来了,上面一大堆都是为了这个家伙的安全执行。

整个Start看完,目的也就是执行MoveNext,那我们看看状态机里MoveNext干了些什么:

看看我们上面翻译的结果:

 1 void IAsyncStateMachine.MoveNext()
2 {
3 int state = State;
4
5 try
6 {
7 TaskAwaiter<string> awaiter;
8
9 if (state != 0) // 状态不是0就进来,默认是-1
10 {
11 Console.WriteLine("start"); // 执行 await 之前的部分
12 awaiter = Program.GetData().GetAwaiter(); // 获取 awaiter
13
14 if (!awaiter.IsCompleted) //判断是否完成,完成的话就不用分开了,直接执行后面的
15 {
16 state = 0;
17 State = 0; // 把状态变为0, awaiter执行完成后再次MoveNext就不用进这里了
18 _taskAwaiter = awaiter; // 保存awaiter, awaiter回来后要靠_taskAwaiter来取结果
19 Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); // 这里面主要是构造一个action - MoveNextRunner,用来在awaiter.complete事件触发后继续走这个状态机的MoveNext(),上面把state变了0了,再走这个函数的话就可以走到await后面的部分,下面再详细讲
20
21 return; // 返回
22 }
23 }
24 else
25 {
26 awaiter = _taskAwaiter;
27 state = -1;
28 State = -1;
29 }
30
31 var result = awaiter.GetResult(); //awaiter回来后取得结果
32 Console.WriteLine(result); // 走 await 后面的部分
33 Console.WriteLine("end");
34 }
35 catch (Exception ex)
36 {
37 State = -2;
38 Builder.SetException(ex);
39 }
40
41 State = -2;
42 Builder.SetResult();
43 }

可以把原始代码看成三段,如图:

第一次进来由于state是-1,所以先执行第一段,接着是第二段,把state置为0并且拿到awaiter做Builder.AwaitUnsafeOnCompleted(ref awaiter, ref this)操作,这个操作里面会在取到数据后再次MoveNext,因为state为0,所以就走到第三段,整个过程是这样。

我们详细看看Builder.AwaitUnsafeOnCompleted这个操作是怎么调用第二次MoveNext的。

 1 public void AwaitOnCompleted<TAwaiter, TStateMachine>(
2 ref TAwaiter awaiter, ref TStateMachine stateMachine)
3 where TAwaiter : INotifyCompletion
4 where TStateMachine : IAsyncStateMachine
5 {
6 try
7 {
8 AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
9 var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
10 Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action.");
11
12 // If this is our first await, such that we've not yet boxed the state machine, do so now.
13 if (m_coreState.m_stateMachine == null)
14 {
15 if (AsyncCausalityTracer.LoggingOn)
16 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0);
17
18 m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null);
19 }
20
21 awaiter.OnCompleted(continuation);
22 }
23 catch (Exception exc)
24 {
25 AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null);
26 }
27 }

一点一点看,先调用了m_coreState.GetCompletionAction,m_coreState是AsyncMethodBuilderCore类型,来看看它的实现:

 1 internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize)
2 {
3 Contract.Assert(m_defaultContextAction == null || m_stateMachine != null,
4 "Expected non-null m_stateMachine on non-null m_defaultContextAction");
5
6 Debugger.NotifyOfCrossThreadDependency();
7
8 var capturedContext = ExecutionContext.FastCapture(); //获取当前线程的ExecutionContext
9 Action action;
10 MoveNextRunner runner;
11 if (capturedContext != null && capturedContext.IsPreAllocatedDefault)
12 {
13 action = m_defaultContextAction;
14 if (action != null)
15 {
16 Contract.Assert(m_stateMachine != null, "If the delegate was set, the state machine should have been as well.");
17 return action;
18 }
19 runner = new MoveNextRunner(capturedContext, m_stateMachine); //new一个MoveNextRunner实例,并把ExecutionContext和状态机传过去
20
21 action = new Action(runner.Run); //runner.Run的action
22 if (taskForTracing != null)
23 {
24 m_defaultContextAction = action = OutputAsyncCausalityEvents(taskForTracing, action);
25 }
26 else
27 {
28 m_defaultContextAction = action;
29 }
30 }
31 else
32 {
33 runner = new MoveNextRunner(capturedContext, m_stateMachine);
34 action = new Action(runner.Run);
35
36 if (taskForTracing != null)
37 {
38 action = OutputAsyncCausalityEvents(taskForTracing, action);
39 }
40 }
41
42 if (m_stateMachine == null)
43 runnerToInitialize = runner;
44
45 return action;
46 }

这段代码看起来比较简单,主要是针对MoveNextRunner实例,传递上下文和状态机给它,大家应该可以猜到MoveNext就是用这个MoveNextRunner.Run去实现了,这个函数返回的就是MoveNextRunner.Run。

再回头看上面的代码,如果m_coreState.m_stateMachine == null,也就是第一次进来就先做PostBoxInitialization操作,看看PostBoxInitialization:

 1 internal void PostBoxInitialization(IAsyncStateMachine stateMachine, MoveNextRunner runner, Task builtTask)
2 {
3 if (builtTask != null)
4 {
5 if (AsyncCausalityTracer.LoggingOn)
6 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, builtTask.Id, "Async: " + stateMachine.GetType().Name, 0);
7
8 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
9 System.Threading.Tasks.Task.AddToActiveTasks(builtTask);
10 }
11
12 m_stateMachine = stateMachine; //给m_stateMachine赋值,因为m_stateMachine是internal IAsyncStateMachine m_stateMachine;这样定义的,所以把struct stateMachine传给这个接口类型时会装箱,目的是在Builder里面保存这个状态机,下次不会走这了
13 m_stateMachine.SetStateMachine(m_stateMachine);
14
15 Contract.Assert(runner.m_stateMachine == null, "The runner's state machine should not yet have been populated.");
16 Contract.Assert(m_stateMachine != null, "The builder's state machine field should have been initialized.");
17
18 runner.m_stateMachine = m_stateMachine;
19 }

这个函数的目的有两个,一个是给状态机装箱保存下来,另一个是给runner的状态机赋值。

再看回上面的AwaitUnsafeOnCompleted函数,到awaiter.UnsafeOnCompleted(continuation)了,这个算是核心,主要就是等这个回来再调用continuation,continuation我们知道是MoveNextRunner的Run函数,先看看这个Run函数:

 1 internal void Run()
2 {
3 Contract.Assert(m_stateMachine != null, "The state machine must have been set before calling Run.");
4
5 if (m_context != null)
6 {
7 try
8 {
9 ContextCallback callback = s_invokeMoveNext;
10 if (callback == null) { s_invokeMoveNext = callback = InvokeMoveNext; }
11
12 ExecutionContext.Run(m_context, callback, m_stateMachine, preserveSyncCtx: true); //主要就是用ExecutionContext应用到当前线程来执行这个((IAsyncStateMachine)stateMachine).MoveNext()
13 }
14 finally { m_context.Dispose(); }
15 }
16 else
17 {
18 m_stateMachine.MoveNext();
19 }
20 }
21
22 private static ContextCallback s_invokeMoveNext;
23
24 private static void InvokeMoveNext(object stateMachine)
25 {
26 ((IAsyncStateMachine)stateMachine).MoveNext();
27 }

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,追根到底看看它是怎么实现的:

https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs

1 public void UnsafeOnCompleted(Action continuation)
2 {
3 OnCompletedInternal(m_task, continuation, continueOnCapturedContext: true, flowExecutionContext: false);
4 }

continueOnCapturedContext这个是由Task.ConfigureAwait(continueOnCapturedContext)来控制的,true则表示执行完task后转到SynchronizationContext所在的线程上去执行await后面的部分,比如说更新UI就必须在UI线程上,这个就需要设为true,如果不是要更新UI,而是还有很多的数据需要本地计算,则最好设为false,这时会在task执行完成后在线程池中拿出一个空闲的工作线程来做await后面的事,当然在Asp.net里要注意HttpContext.Current可能在false时会为Null,操作时需要注意。接着看OnCompletedInternal的代码:

 1 internal static void OnCompletedInternal(Task task, Action continuation, bool continueOnCapturedContext, bool flowExecutionContext)
2 {
3 if (continuation == null) throw new ArgumentNullException("continuation");
4 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
5
6 if (TplEtwProvider.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
7 {
8 continuation = OutputWaitEtwEvents(task, continuation);
9 }
10
11 task.SetContinuationForAwait(continuation, continueOnCapturedContext, flowExecutionContext, ref stackMark);
12 }

主要是调用SetContinuationForAwait:

 1 internal void SetContinuationForAwait(
2 Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark)
3 {
4 Contract.Requires(continuationAction != null);
5
6
7 TaskContinuation tc = null;
8
9 if (continueOnCapturedContext) //如果需要用到SynchronizationContext
10 {
11 var syncCtx = SynchronizationContext.CurrentNoFlow; //获取当前SynchronizationContext
12 if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext)) //当前SynchronizationContext和传进来的SynchronizationContext不相等
13 {
14 tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction, flowExecutionContext, ref stackMark); //用SynchronizationContext来转到目标线程去执行
15 }
16 Else
17 {
18 var scheduler = TaskScheduler.InternalCurrent;
19 if (scheduler != null && scheduler != TaskScheduler.Default)
20 {
21 tc = new TaskSchedulerAwaitTaskContinuation(scheduler, continuationAction, flowExecutionContext, ref stackMark);
22 }
23 }
24 }
25
26 if (tc == null && flowExecutionContext)
27 {
28 tc = new AwaitTaskContinuation(continuationAction, flowExecutionContext: true, stackMark: ref stackMark); // continueOnCapturedContext = false时
29 }
30
31 if (tc != null)
32 {
33 if (!AddTaskContinuation(tc, addBeforeOthers: false))
34 tc.Run(this, bCanInlineContinuationTask: false); //开始执行Run
35 }
36 else
37 {
38 Contract.Assert(!flowExecutionContext, "We already determined we're not required to flow context.");
39 if (!AddTaskContinuation(continuationAction, addBeforeOthers: false))
40 AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this);
41 }
42 }

最主要看是怎么Run的,先看第一种,continueOnCapturedContext为true的:

 1 internal sealed override void Run(Task task, bool canInlineContinuationTask)
2 {
3 if (canInlineContinuationTask && this.m_syncContext == SynchronizationContext.CurrentNoFlow) //如果当前线程的SynchronizationContext和syncContext一样,那表示就是一个线程,直接执行就好了
4 {
5 base.RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), this.m_action, ref Task.t_currentTask);
6 return;
7 }
8 TplEtwProvider log = TplEtwProvider.Log;
9 if (log.IsEnabled())
10 {
11 this.m_continuationId = Task.NewId();
12 log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, this.m_continuationId);
13 }
14 base.RunCallback(SynchronizationContextAwaitTaskContinuation.GetPostActionCallback(), this, ref Task.t_currentTask); // 这里用到了GetPostActionCallback()来执行
15 }

看看PostAction:

 1 private static void PostAction(object state)
2 {
3 SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = (SynchronizationContextAwaitTaskContinuation)state;
4 if (TplEtwProvider.Log.TasksSetActivityIds && synchronizationContextAwaitTaskContinuation.m_continuationId != 0)
5 {
6 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线程执行
7 return;
8 }
9 synchronizationContextAwaitTaskContinuation.m_syncContext.Post(SynchronizationContextAwaitTaskContinuation.s_postCallback, synchronizationContextAwaitTaskContinuation.m_action);
10 }

来看看第二种:continueOnCapturedContext为false:

 1 internal override void Run(Task task, bool canInlineContinuationTask)
2 {
3 if (canInlineContinuationTask && AwaitTaskContinuation.IsValidLocationForInlining)
4 {
5 this.RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), this.m_action, ref Task.t_currentTask); //这里去到RunCallback
6 return;
7 }
8 TplEtwProvider log = TplEtwProvider.Log;
9 if (log.IsEnabled())
10 {
11 this.m_continuationId = Task.NewId();
12 log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, this.m_continuationId);
13 }
14 ThreadPool.UnsafeQueueCustomWorkItem(this, false); // 这也是通过线程池去运行
15 }
16
17 protected void RunCallback(ContextCallback callback, object state, ref Task currentTask)
18 {
19 Task task = currentTask;
20 try
21 {
22 if (task != null)
23 {
24 currentTask = null;
25 }
26 if (this.m_capturedContext == null)
27 {
28 callback(state);
29 }
30 else
31 {
32 ExecutionContext.Run(this.m_capturedContext, callback, state, true); //就是通过ExecutionContext.Run去运行
33 }
34 }
35 catch (Exception arg_2A_0)
36 {
37 AwaitTaskContinuation.ThrowAsyncIfNecessary(arg_2A_0);
38 }
39 finally
40 {
41 if (task != null)
42 {
43 currentTask = task;
44 }
45 if (this.m_capturedContext != null)
46 {
47 this.m_capturedContext.Dispose();
48 }
49 }
50 }

所以为false时就没SynchronizationContext什么事,线程池里拿个空闲线程出来运行就好了。上面有很大篇幅讲了awaiter.AwaitUnsafeOnCompleted的运行原理,因为async/await是配合awaitable用的,所以就一起分析。

那现在这个简单的async/await例子就分析完了,可能有人会觉得状态机貌似没什么用,用if/else也能轻松做到这个,没必要用MoveNext。那是因为这里只有一个await,如果更多呢,if/else就很难控制,MoveNext就只需要关注状态变化就好了。写个有三个await的函数来看看:

 1 static async void DisplayDataAsync()
2 {
3 Console.WriteLine("start");
4
5 Console.WriteLine("progress_1");
6 await GetData();
7
8 Console.WriteLine("progress_2");
9 await GetData();
10
11 Console.WriteLine("progress_3");
12 await GetData();
13
14 Console.WriteLine("end");
15 }

因为IL上面已经讲过,多个await的指令其实差不多,所以用另一种简单的方法:ILSpy来直接看翻译结果,需要在Options里把Decompile async method(async/await)关掉,如图:

MoveNext的代码:

 1 void IAsyncStateMachine.MoveNext()
2 {
3 int num = this.<> 1__state;
4 try
5 {
6 TaskAwaiter<string> taskAwaiter;
7 switch (num)
8 {
9 case 0:
10 taskAwaiter = this.<> u__1;
11 this.<> u__1 = default(TaskAwaiter<string>);
12 this.<> 1__state = -1;
13 break;
14 case 1:
15 taskAwaiter = this.<> u__1;
16 this.<> u__1 = default(TaskAwaiter<string>);
17 this.<> 1__state = -1;
18 goto IL_ED;
19 case 2:
20 taskAwaiter = this.<> u__1;
21 this.<> u__1 = default(TaskAwaiter<string>);
22 this.<> 1__state = -1;
23 goto IL_157;
24 default:
25 Console.WriteLine("start");
26 Console.WriteLine("progress_1");
27 taskAwaiter = Program.GetData().GetAwaiter();
28 if (!taskAwaiter.IsCompleted)
29 {
30 this.<> 1__state = 0;
31 this.<> u__1 = taskAwaiter;
32 this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this);
33 return;
34 }
35 break;
36 }
37 taskAwaiter.GetResult();
38 taskAwaiter = default(TaskAwaiter<string>);
39 Console.WriteLine("progress_2");
40 taskAwaiter = Program.GetData().GetAwaiter();
41 if (!taskAwaiter.IsCompleted)
42 {
43 this.<> 1__state = 1;
44 this.<> u__1 = taskAwaiter;
45 this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this);
46 return;
47 }
48 IL_ED:
49 taskAwaiter.GetResult();
50 taskAwaiter = default(TaskAwaiter<string>);
51 Console.WriteLine("progress_3");
52 taskAwaiter = Program.GetData().GetAwaiter();
53 if (!taskAwaiter.IsCompleted)
54 {
55 this.<> 1__state = 2;
56 this.<> u__1 = taskAwaiter;
57 this.<> t__builder.AwaitUnsafeOnCompleted < TaskAwaiter<string>, Program.< DisplayDataAsync > d__1 > (ref taskAwaiter, ref this);
58 return;
59 }
60 IL_157:
61 taskAwaiter.GetResult();
62 taskAwaiter = default(TaskAwaiter<string>);
63 Console.WriteLine("end");
64 }
65 catch (Exception exception)
66 {
67 this.<> 1__state = -2;
68 this.<> t__builder.SetException(exception);
69 return;
70 }
71 this.<> 1__state = -2;
72 this.<> t__builder.SetResult();
73 }

还是比较容易理解,思路和单个await一样,这里通过goto的方式来控制流程,很聪明的做法,这样既可以跳转,又不影响taskAwaiter.IsCompleted为true时的直接运行。

在讲AsyncVoidMethodBuilder.Create时讲到SynchronizationContext的用处是处理异常,那现在来看看AsyncVoidMethodBuilder的异常处理:

 1 internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext)
2 {
3 var edi = ExceptionDispatchInfo.Capture(exception);
4
5 if (targetContext != null)
6 {
7 try
8 {
9 targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi);
10 return;
11 }
12 catch (Exception postException)
13 {
14 edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException));
15 }
16 }
17 }

看到了吧,把异常通过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块中轻松捕获。

 
 
 

进阶篇:以IL为剑,直指async/await的更多相关文章

  1. 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法

    什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...

  2. [.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程

    怎样使用 async & await 一步步将同步代码转换为异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6079707.html  ...

  3. [译]async/await中阻塞死锁

    这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的两篇博文中翻译过来. 原文1:Don'tBlock o ...

  4. [译]async/await中使用阻塞式代码导致死锁 百万数据排序:优化的选择排序(堆排序)

    [译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的 ...

  5. [译]async/await中使用阻塞式代码导致死锁

    原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Clea ...

  6. 深入理解协程(三):async/await实现异步协程

    原创不易,转载请联系作者 深入理解协程分为三部分进行讲解: 协程的引入 yield from实现异步协程 async/await实现异步协程 本篇为深入理解协程系列文章的最后一篇. 从本篇你将了解到: ...

  7. JavaScript async/await:优点、陷阱及如何使用

    翻译练习 原博客地址:JavaScript async/await: The Good Part, Pitfalls and How to Use ES7中引进的async/await是对JavaSc ...

  8. 【从零开始搭建自己的.NET Core Api框架】(七)授权认证进阶篇

    系列目录 一.  创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...

  9. 微信小程序开发——进阶篇

    由于项目的原因,最近的工作一直围绕着微信小程序.现在也算告一段落,是时候整理一下这段时间的收获了.我维护的小程序有两个,分别是官方小程序和一个游戏为主的小程序.两个都是用了wepy进行开发,就是这个: ...

随机推荐

  1. String和StringBuffer

    已知在Java中,boolean.byte.short.int.long.char.float.double这八种是基本数据类型,其余的都是引用类型,比如String.数组.接口.对象等. 当我们声明 ...

  2. Woodbury matrix identity

    woodbury matrix identity 2014/6/20 [转载请注明出处]http://www.cnblogs.com/mashiqi http://en.wikipedia.org/w ...

  3. C# button 去边框

    Button属性里找到FlatStyle属性:Flat FlatAppearance BorderSize:0;

  4. codeforces105d Bag of mice ——概率DP

    Link: http://codeforces.com/problemset/problem/148/D Refer to: http://www.cnblogs.com/kuangbin/archi ...

  5. JavaSE坦克网络版

    02.1.建立Server(保持这个TankServer一直运行) package server; public class TankServer { public static void main( ...

  6. LeetCode【217. Contains Duplicate】

    Given an array of integers, find if the array contains any duplicates. Your function should return t ...

  7. strcat函数的使用需要注意的问题

    曾被这个函数困扰了好久,然后各种假设,验证:但是最后却发现这个函数并没有什么好讲的,原来的过错一切都源于忽略了“*dst去掉\0,然后加上*src,最后返回*dst”这句话的真正含义:给*dst分配的 ...

  8. (转) Deep Reinforcement Learning: Playing a Racing Game

    Byte Tank Posts Archive Deep Reinforcement Learning: Playing a Racing Game OCT 6TH, 2016 Agent playi ...

  9. EDIUS设置Alpha转场的教程

    有刚开始学习EDIUS视频编辑软件的同学吗?你们是否需要一本很好的EDIUS教程呢?你们可以到EDIUS中文网站里面找哦,小编会一直更新EDIUS教程的,能给你们带来帮助我是非常高兴的.今天我们来一起 ...

  10. osx 文本编辑工具下载地址Sublime Text 3

    下载地址: http://www.sublimetext.com/3 Sublime Text 是一个代码编辑器(Sublime Text 3是收费软件,但可以无限期试用),也是HTML和散文先进的文 ...