简单说说Task Async Await
网上有太多关于task async await前世今生的帖子,我这里就直接进入主题吧,大概分以下几个部分来简单聊聊异步编程的原理实现。1.task执行源码解读,看看微软底层对task的实现和thread有啥关系和区别。2.从il代码层面看看编译器对task和async await做了啥操作,以至于语法这么先进就能实现异步编程。3.为啥async修饰的方法返回值必须是规定的类型。4.解读task async await执行逻辑。以上就是今天我要简单聊的内容。
Task执行源码解读
首先非常感谢微软拥抱开源这一壮举啊,有兴趣的朋友可以到github上面下载runtime源码,地址: https://github.com/dotnet/runtime 。还有一种方式,在线浏览源码,地址 https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,c39f4253ff7cb1ee 。我们就从入口run & start这两个方法开始吧,直接贴源码,一步一步跟踪,看看微软对task的部分实现。
1 public static Task Run(Action action) 2 { 3 return InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default, TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None); 4 }
这就是我们经常调用task的run方法,在这个方法里面我们先留意一下TaskScheduler.Default这个对象,后续再说,我们继续跟踪执行主方法StartNew里面的实现。看代码
1 internal static Task<TResult> InternalStartNew(Task parent, Func<TResult> function, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler) 2 { 3 // 其他代码... 4 Task<TResult> task = new Task<TResult>(function, parent, cancellationToken, creationOptions, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler); 5 task.ScheduleAndStart(needsProtection: false); 6 return task; 7 }
在创建task实例时,指定了parent参数,默认是null,说明task是有层级关系的,这里需要注意task的返回时机,对我们后续理解awaiter有帮助,我们继续跟踪ScheduleAndStart方法。
1 internal void ScheduleAndStart(bool needsProtection) 2 { 3 // 其他代码... 4 try 5 { 6 m_taskScheduler.InternalQueueTask(this); 7 } 8 catch (Exception innerException) 9 { 10 TaskSchedulerException ex = new TaskSchedulerException(innerException); 11 AddException(ex); 12 Finish(userDelegateExecute: false); 13 if ((Options & (TaskCreationOptions)512) == 0) 14 { 15 m_contingentProperties.m_exceptionsHolder.MarkAsHandled(calledFromFinalizer: false); 16 } 17 throw ex; 18 } 19 }
这里直接调用了TaskScheduler.Default这个对象的InternalQueueTask方法,上面我有说留意这个scheduler对象,最终我们的task就是通过这个scheduler得以执行,我们看看它的定义
1 public abstract class TaskScheduler 2 { 3 protected internal abstract void QueueTask(Task task); 4 5 // 其他成员.... 6 7 internal void InternalQueueTask(Task task) 8 { 9 QueueTask(task); 10 } 11 12 13 }
以上代码我把其他成员全部删了,它是一个抽象类,最终我们task的执行是由taskscheduler的实现类通过多态的方式得以执行,我们继续看看它的默认实现类 ThreadPoolTaskScheduler的简单定义。
1 internal sealed class ThreadPoolTaskScheduler : TaskScheduler 2 { 3 // 其他成员... 4 protected internal override void QueueTask(Task task) 5 { 6 TaskCreationOptions options = task.Options; 7 if ((options & TaskCreationOptions.LongRunning) != 0) 8 { 9 Thread thread = new Thread(s_longRunningThreadWork); 10 thread.IsBackground = true; 11 thread.Start(task); 12 } 13 else 14 { 15 bool preferLocal = (options & TaskCreationOptions.PreferFairness) == 0; 16 ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal); 17 } 18 } 19 }
代码比较简单,默认情况下我们的task是以threadpool的方式执行,由clr管理。当然你也可以通过设置参数以thread的方式执行。不知道是否有朋友跟我一样,在刚接触task的时候,调用task的run方法总比thread的start方法慢半拍甚至更长。task的执行就简单说到这,接着我们简单聊聊async & await结合task实现异步编程。
从IL代码层面看看编译器对task和async await做了啥操作?下面我简单贴一下,我的测试代码,没有什么含义,重在看看编译器做了啥。
1 static void Main(string[] args) 2 { 3 TestAsync(); 4 Console.WriteLine("Hello World!"); 5 } 6 7 8 public static async void TestAsync() 9 { 10 Console.WriteLine("start get string"); 11 var r1 = await GetDataString(); 12 Console.WriteLine(r1); 13 14 15 Console.WriteLine("end complete"); 16 } 17 18 19 public static async Task<string> GetDataString() 20 { 21 return await Task.Run<string>(async () => 22 { 23 { 24 await Task.Delay(5000); 25 return "get data complete"; 26 } 27 }); 28 }
以上测试代码不用描述了吧,很简单的代码,没啥意义,接下来我们通过ildasm工具来查看以上源码编译的il代码。
上面我就截了一个图,从图来看还是比较犀利的,我们上面的代码被编译成这样了,多了2个类型,d__这些,那么我们是否可以这么理解,编译器在编译过程中,如果发现方法有async修饰,就会被编译成一个类型class。我们下面直接读il代码吧,从main函数开始。看代码
1 .method private hidebysig static void Main(string[] args) cil managed 2 { 3 .entrypoint 4 // 代码大小 19 (0x13) 5 .maxstack 8 6 IL_0000: nop 7 IL_0001: call void ConsoleApp2.Program::TestAsync() // 调用TestAsync() 8 IL_0006: nop 9 IL_0007: ldstr "Hello World!" // 加载字符串到Hello World! 10 IL_000c: call void [System.Console]System.Console::WriteLine(string) // 打印Hello World! 11 IL_0011: nop 12 IL_0012: ret // 退出 13 } // end of method Program::Main
il代码的解说我直接在代码后面注释了,我们继续看testasync方法里面的il代码。
1 .method public hidebysig static void TestAsync() cil managed 2 { 3 .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( 01 00 23 43 6F 6E 73 6F 6C 65 41 70 70 32 2E 50 // ..#ConsoleApp2.P 4 72 6F 67 72 61 6D 2B 3C 54 65 73 74 41 73 79 6E // rogram+<TestAsyn 5 63 3E 64 5F 5F 31 00 00 ) // c>d__1.. 6 .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) 7 // 代码大小 38 (0x26) 8 .maxstack 2 9 .locals init (class ConsoleApp2.Program/'<TestAsync>d__1' V_0) 声明<TestAsync>d__1 v_0变量 10 IL_0000: newobj instance void ConsoleApp2.Program/'<TestAsync>d__1'::.ctor() // 创建<TestAsync>d__1实例 11 IL_0005: stloc.0 赋值到v_0 = new <TestAsync>d__1(); 12 IL_0006: ldloc.0 加载第一个变量 13 IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create() 调用v_0的create函数 14 IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 调用v_0的create函数的返回值赋值给<>t__builder 15 IL_0011: ldloc.0 16 IL_0012: ldc.i4.m1 加载int32数值-1 17 IL_0013: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把-1赋值给<>1__state 18 IL_0018: ldloc.0 19 IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 20 IL_001e: ldloca.s V_0 加载v_0地址 21 IL_0020: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<class ConsoleApp2.Program/'<TestAsync>d__1'>(!!0&) 调用V_0.<>t__builder.start方法并传递参数引用 ref V_0 22 IL_0025: ret 23 } // end of method Program::TestAsync
到此,我们的手撸代码好像还没看到,不知道哪里去了,il代码的解说需要暂停一下,我们通过查看il代码发现最终调用了AsyncVoidMethodBuilder的start方法,我这里继续跟踪start方法,看看start方法里面做了啥。
1 public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 2 { 3 // 其他代码... 4 Thread currentThread = Thread.CurrentThread; 5 Thread thread = currentThread; 6 ExecutionContext executionContext = currentThread._executionContext; 7 ExecutionContext executionContext2 = executionContext; 8 SynchronizationContext synchronizationContext = currentThread._synchronizationContext; 9 try 10 { 11 stateMachine.MoveNext(); 12 } 13 finally 14 { 15 SynchronizationContext synchronizationContext2 = synchronizationContext; 16 Thread thread2 = thread; 17 if (synchronizationContext2 != thread2._synchronizationContext) 18 { 19 thread2._synchronizationContext = synchronizationContext2; 20 } 21 ExecutionContext executionContext3 = executionContext2; 22 ExecutionContext executionContext4 = thread2._executionContext; 23 if (executionContext3 != executionContext4) 24 { 25 ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4); 26 } 27 } 28 }
这里我们暂时先关注MoveNext方法的调用,stateMachine是start方法传递过来的参数,也就是说此处调用的moveNext方法就是,以上分析il代码,编译器为我们创建的<TestAsync>d__1对象实现的moveNext方法,感觉我的描述有点拗,继续跟踪又回到了il代码,先看下编译器为我们生成的类型<TestAsync>d__1的定义。
1 .class auto ansi sealed nested private beforefieldinit '<TestAsync>d__1' 2 extends [System.Runtime]System.Object 3 implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine 4 { 5 .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 6 } // end of class '<TestAsync>d__1'
其实就是一个IAsyncStateMachine,名叫状态机。我们继续看看moveNext函数。
1 .method private final hidebysig newslot virtual 2 instance void MoveNext () cil managed 3 { 4 5 .locals init ( 6 [0] int32, 7 [1] valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string>, 8 [2] class ConsoleApp2.Program/'<TestAsync>d__1', 9 [3] class [System.Runtime]System.Exception 10 ) 定义4个变量 11 12 13 IL_0000: ldarg.0 加载当前引用this 14 IL_0001: ldfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 加载<>1__state字段 15 IL_0006: stloc.0 把<>1__state赋值给第一个参数 16 .try try块 17 { 18 IL_0007: ldloc.0 加载第一个参数 19 IL_0008: brfalse.s IL_000c 如果为0 或者 false 跳转到IL_000c地址执行 20 IL_000a: br.s IL_000e 否则跳转到IL_000e地址执行 21 IL_000c: br.s IL_0055 接上上面跳转 22 IL_000e: nop 23 IL_000f: ldstr "start get string" 加载字符串到栈顶 24 IL_0014: call void [System.Console]System.Console::WriteLine(string) 打印栈顶字符串 25 IL_0019: nop 26 IL_001a: call class [System.Runtime]System.Threading.Tasks.Task`1<string> ConsoleApp2.Program::GetDataString() 调用GetDataString()函数 27 IL_001f: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [System.Runtime]System.Threading.Tasks.Task`1<string>::GetAwaiter() 通过多态的方式调用GetDataString()返回值task的GetAwaiter()函数 28 IL_0024: stloc.1 赋值给第二个变量 29 IL_0025: ldloca.s 1 加载第二个变量的地址 30 IL_0027: call instance bool valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string>::get_IsCompleted() 调用第二个变量地址的get属性,获取IsCompleted属性的值 31 IL_002c: brtrue.s IL_0071 返回值为true 跳转到IL_0071 32 33 34 IL_002e: ldarg.0 如果为false,接着往下走, 加载this指针 35 IL_002f: ldc.i4.0 加载int32 0到栈顶 36 IL_0030: dup 复制备份 37 IL_0031: stloc.0 存储到第一个变量 38 IL_0032: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把第一个变量的值0 复制给<>1__state字段 39 IL_0037: ldarg.0 加载this指针 40 IL_0038: ldloc.1 加载第二个变量 41 IL_0039: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string> ConsoleApp2.Program/'<TestAsync>d__1'::'<>u__1' 把第二个变量复制到<>u__1字段 42 IL_003e: ldarg.0 this指针 43 IL_003f: stloc.2 把this赋值给第三个变量 44 IL_0040: ldarg.0 加载this 45 IL_0041: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 加载<>t__builder字段 46 IL_0046: ldloca.s 1 加载第二个变量地址 47 IL_0048: ldloca.s 2 加载三个变量地址 你可以理解为 为下一个操作传递引用ref 48 IL_004a: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::AwaitUnsafeOnCompleted<valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string>, class ConsoleApp2.Program/'<TestAsync>d__1'>(!!0&, !!1&) 调用<>t__builder.AwaitUnsafeOnCompleted函数并通过ref传递第二三个变量 49 IL_004f: nop 50 IL_0050: leave IL_00e4 return出去 51 52 53 IL_0055: ldarg.0 加载this 54 IL_0056: ldfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string> ConsoleApp2.Program/'<TestAsync>d__1'::'<>u__1' 加载字段<>u__1 55 IL_005b: stloc.1 把<>u__1字段赋值给第二参数 56 IL_005c: ldarg.0 加载this 57 IL_005d: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string> ConsoleApp2.Program/'<TestAsync>d__1'::'<>u__1' 加载<>u__1字段 58 IL_0062: initobj valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string> 初始化<>u__1字段 59 IL_0068: ldarg.0 加载this 60 IL_0069: ldc.i4.m1 加载int32 -1 61 IL_006a: dup 复制 62 IL_006b: stloc.0 存储到第一个变量 63 IL_006c: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 同事把第一个变量赋值到<>1__state字段 64 65 66 IL_0071: ldarg.0 加载this 67 IL_0072: ldloca.s 1 加载第二个变量地址 68 IL_0074: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<string>::GetResult() 调用第二个变量的GetResult() 函数 69 IL_0079: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<>s__2' 把返回值赋值给<>s__2字段 70 IL_007e: ldarg.0 71 IL_007f: ldarg.0 72 IL_0080: ldfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<>s__2' 加载<>s__2字段 73 IL_0085: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<r1>5__1' 把<>s__2字段赋值给<r1>5__1字段 74 IL_008a: ldarg.0 75 IL_008b: ldnull 加载null 76 IL_008c: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<>s__2' 把null赋值给<>s__2字段 77 IL_0091: ldarg.0 78 IL_0092: ldfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<r1>5__1' 加载字段<r1>5__1 79 IL_0097: call void [System.Console]System.Console::WriteLine(string) 打印字段<r1>5__1 80 IL_009c: nop 81 IL_009d: ldstr "end complete" 加载字符串end complete 82 IL_00a2: call void [System.Console]System.Console::WriteLine(string) 打印字符串 83 IL_00a7: nop 84 IL_00a8: leave.s IL_00c9 return弹栈 85 } // end .try 86 catch [System.Runtime]System.Exception 87 { 88 IL_00aa: stloc.3 把exception赋值给第四个参数 89 IL_00ab: ldarg.0 90 IL_00ac: ldc.i4.s -2 加载-2 91 IL_00ae: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把-2赋值给<>1__state字段 92 IL_00b3: ldarg.0 93 IL_00b4: ldnull 加载null 94 IL_00b5: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<r1>5__1' 把null赋值给<r1>5__1字段 95 IL_00ba: ldarg.0 96 IL_00bb: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 加载字段<>t__builder 97 IL_00c0: ldloc.3 加载4个变量 98 IL_00c1: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetException(class [System.Runtime]System.Exception) 调用<>t__builder字段的SetException函数 99 IL_00c6: nop 100 IL_00c7: leave.s IL_00e4 弹栈 101 } // end handler 102 103 104 IL_00c9: ldarg.0 105 IL_00ca: ldc.i4.s -2 加载-2 106 IL_00cc: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把-2赋值给<>1__state字段 107 IL_00d1: ldarg.0 108 IL_00d2: ldnull 109 IL_00d3: stfld string ConsoleApp2.Program/'<TestAsync>d__1'::'<r1>5__1' 把null赋值给<r1>5__1 110 IL_00d8: ldarg.0 111 IL_00d9: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 加载<>t__builder字段 112 IL_00de: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetResult() 调用<>t__builder字段的SetResult()函数 113 IL_00e3: nop 114 115 116 IL_00e4: ret 弹栈 117 } // end of method '<TestAsync>d__1'::MoveNext
以上il代码就是moveNext方法的执行逻辑,其实读il代码我们要按cpu执行机器码指令去解读,当然这不是真正的cpu指令,但是你真正去逆向非托管代码,其执行指令解读方式都差不多,但是难度比这个大,本人之前简单逆向了加密狗的lincens,当然那个比较简单,稍微复杂点我也搞不定。我们知道非托管代码编译是丢弃了源码,直接编译成本地机器码,高手是可以通过读机器码还原大部分源代码,还原不是目的,目的是破解,好了,牛逼不吹了,继续我们的任务。通过以上il代码,我们简单还原一下部分代码。
1 class <TestAsync>d__1{ 2 int32 '<>1__state' 3 AsyncVoidMethodBuilder '<>t__builder' 4 string '<r1>5__1' 5 string '<>s__2' 6 TaskAwaiter<string> '<>u__1' 7 void MoveNext () 8 { 9 int num; 10 TaskAwaiter<string> awaiter ; 11 <TestAsync>d__1 stateMachine; 12 Exception exception; 13 num = <>1__state; 14 try 15 { 16 if (num != 0) 17 { 18 Console.WriteLine("start get string"); 19 awaiter = GetDataString().GetAwaiter(); 20 if (!awaiter.IsCompleted) 21 { 22 num = 0; 23 <>1__state =0; 24 <>u__1 = awaiter; 25 <TestAsync>d__1 stateMachine = this; 26 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 27 return; 28 } 29 } 30 else{ 31 awaiter = <>u__1; 32 <>u__1 = default(TaskAwaiter<string>); 33 num = (<>1__state = -1); 34 <>s__2 = awaiter.GetResult(); 35 36 <r1>5__1 = <>s__2; 37 <>s__2 = null; 38 Console.WriteLine(<r1>5__1); 39 Console.WriteLine("end complete"); 40 return; 41 } 42 } 43 catch() 44 { 45 // todo.... 46 } 47 } 48 }
以上就是针对il代码做的简单还原,你也可以理解为伪代码,在这个调用堆栈里面只有两个await,所以在状态机里面只有两个状态,这以为着await越多,状态就越多,逻辑就越复杂。简单总结下,1.编译器会把async修饰的方法,编译成class并派生自IAsyncStateMachine,2.await决定状态机里面有多少个状态,当然在movenext范围内。好了il代码就看到这里,还原不是目的,目的是理清执行逻辑以及在逻辑里面涉及到的一些底层的实现。有了执行逻辑,接下来我们看看,微软到底干了啥?
async & await执行,接着上面源码跟踪的start方法,在start方法里面,执行了moveNext方法。moveNext方法的实现逻辑以及涉及到的对象,我们也通过il部分还原了。回顾一下start方法,怕大家忘记,我这里从新贴一下代码
1 .method public hidebysig static void TestAsync() cil managed 2 { 3 4 .maxstack 2 5 .locals init (class ConsoleApp2.Program/'<TestAsync>d__1' V_0) 声明<TestAsync>d__1 v_0变量 6 IL_0000: newobj instance void ConsoleApp2.Program/'<TestAsync>d__1'::.ctor() // 创建<TestAsync>d__1实例 7 IL_0005: stloc.0 赋值到v_0 = new <TestAsync>d__1(); 8 IL_0006: ldloc.0 加载第一个变量 9 IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create() 调用v_0的create函数 10 IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 调用v_0的create函数的返回值赋值给<>t__builder 11 IL_0011: ldloc.0 12 IL_0012: ldc.i4.m1 加载int32数值-1 13 IL_0013: stfld int32 ConsoleApp2.Program/'<TestAsync>d__1'::'<>1__state' 把-1赋值给<>1__state 14 IL_0018: ldloc.0 15 IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder ConsoleApp2.Program/'<TestAsync>d__1'::'<>t__builder' 16 IL_001e: ldloca.s V_0 加载v_0地址 17 IL_0020: call instance void [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<class ConsoleApp2.Program/'<TestAsync>d__1'>(!!0&) 调用V_0.<>t__builder.start方法并传递参数引用 ref V_0 18 IL_0025: ret 19 } // end of method Program::TestAsync
最终通过调用<>t__builder.start方法启动入口,<>t__builder类型为AsyncVoidMethodBuilder,我们先看下它的定义。
1 public struct AsyncVoidMethodBuilder 2 { 3 // 其他成员... 4 private SynchronizationContext _synchronizationContext; 5 private AsyncTaskMethodBuilder _builder; 6 private Task Task => _builder.Task; 7 internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger; 8 public static AsyncVoidMethodBuilder Create() 9 { 10 SynchronizationContext current = SynchronizationContext.Current; 11 current?.OperationStarted(); 12 AsyncVoidMethodBuilder result = default(AsyncVoidMethodBuilder); 13 result._synchronizationContext = current; 14 return result; 15 } 16 17 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 18 { 19 AsyncMethodBuilderCore.Start(ref stateMachine); 20 } 21 }
它实际就是一个结构体,除了它,还有其他两个结构体,分别为AsyncTaskMethodBuilder,AsyncTaskMethodBuilder<T>,其实看命名就大概能猜到了,根据返回值的不同而不同,这个后面会细说,我们继续start方法。通过调用AsyncMethodBuilderCore对象的同名函数start实现,在AsyncMethodBuilderCore.start方法里面调用了我们上面还原的moveNext方法。我们重点看movenext方法。
1 if (num != 0) 2 { 3 Console.WriteLine("start get string"); 4 awaiter = GetDataString().GetAwaiter(); 5 if (!awaiter.IsCompleted) 6 { 7 num = 0; 8 <>1__state =0; 9 <>u__1 = awaiter; 10 <TestAsync>d__1 stateMachine = this; 11 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 12 return; 13 } 14 }
第一次调用,num为-1,也就是!=0,打印字符串,接着获取awaiter对象,这个地方需要注意一下,由于我在GetDataString方法里面启动了新的task,结合上面解说的task执行逻辑,在task还未真正执行就返回这个task,并且获得该task的awaiter对象。如果我在GetDataString没有开启task,那么就会同步执行,并且不会自己去开新线程。我们继续看,接着判断awaiter.IsCompleted,实际就是获取task的状态,这里我们延时5s,正常返回的是false,接着执行AwaitUnsafeOnCompleted方法。我们继续看AwaitUnsafeOnCompleted方法的实现逻辑。
1 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine 2 { 3 IAsyncStateMachineBox stateMachineBox = GetStateMachineBox(ref stateMachine); // 有兴趣的朋友自己去看源码,我这里简单描述一下,整个这一块还是比较复杂的,包装当前状态机<TestAsync>d__1以及当前线程上下文,这个线程在console里面叫主线程上下文,以及初始化MoveNextAction,最后hook等待状态机的状态变化. 4 if (default(TAwaiter) != null && awaiter is ITaskAwaiter) // 默认我们走的这个分支 5 { 6 TaskAwaiter.UnsafeOnCompletedInternal(Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter).m_task, stateMachineBox, continueOnCapturedContext: true); 7 } 8 else if (default(TAwaiter) != null && awaiter is IConfiguredTaskAwaiter) 9 { 10 11 } 12 else if (default(TAwaiter) != null && awaiter is IStateMachineBoxAwareAwaiter) 13 { 14 15 } 16 else 17 { 18 19 } 20 }
上面代码涉及到逻辑的地方,我在后面有相关注释,这里我还是贴一下IAsyncStateMachineBox接口的定义.
1 internal interface IAsyncStateMachineBox 2 { 3 Action MoveNextAction // 默认绑定到moveNext 4 { 5 get; 6 } 7 8 9 void MoveNext(); // 状态机状态发生变更 10 11 12 IAsyncStateMachine GetStateMachineObject(); 13 }
通过上面代码跟踪发现,任务状态已经变更(此处为完成,如果多个await会有多个状态),接下来的操作是不是要执行await之后的逻辑?我们继续看看TaskAwaiter.UnsafeOnCompletedInternal的实现.
1 internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext) 2 { 3 if (continueOnCapturedContext) 4 { 5 SynchronizationContext current = SynchronizationContext.Current; //获取当前线程 6 if (current != null && current.GetType() != typeof(SynchronizationContext)) 7 { 8 SynchronizationContextAwaitTaskContinuation synchronizationContextAwaitTaskContinuation = new SynchronizationContextAwaitTaskContinuation(current, stateMachineBox.MoveNextAction, flowExecutionContext: false); 9 if (!AddTaskContinuation(synchronizationContextAwaitTaskContinuation, addBeforeOthers: false)) 10 { 11 synchronizationContextAwaitTaskContinuation.Run(this, canInlineContinuationTask: false); // 此处调用run 12 } 13 return; 14 } 15 TaskScheduler internalCurrent = TaskScheduler.InternalCurrent; 16 if (internalCurrent != null && internalCurrent != TaskScheduler.Default) 17 { 18 TaskSchedulerAwaitTaskContinuation taskSchedulerAwaitTaskContinuation = new TaskSchedulerAwaitTaskContinuation(internalCurrent, stateMachineBox.MoveNextAction, flowExecutionContext: false); 19 if (!AddTaskContinuation(taskSchedulerAwaitTaskContinuation, addBeforeOthers: false)) 20 { 21 taskSchedulerAwaitTaskContinuation.Run(this, canInlineContinuationTask: false); 22 } 23 return; 24 } 25 } 26 if (!AddTaskContinuation(stateMachineBox, addBeforeOthers: false)) 27 { 28 ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, preferLocal: true); 29 } 30 }
我们继续跟踪synchronizationContextAwaitTaskContinuation.Run方法.
1 internal sealed override void Run(Task task, bool canInlineContinuationTask) 2 { 3 if (canInlineContinuationTask && m_syncContext == SynchronizationContext.Current) // 判断线程上下文环境,如果当前线程和前面设置的线程上下文环境一致,后续的操作还是由当前线程处理,为什么有这个需求?cs开发的朋友知道,有个线程安全问题 4 { 5 RunCallback(AwaitTaskContinuation.GetInvokeActionCallback(), m_action, ref Task.t_currentTask); // 注意这个m_action,是前面绑定的moveNext.action, GetInvokeActionCallback方法 6 return; 7 } 8 TplEventSource log = TplEventSource.Log; 9 if (log.IsEnabled()) 10 { 11 m_continuationId = Task.NewId(); 12 log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId); 13 } 14 RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask); 15 }
以上代码最终调用的是RunCallback方法,继续看.
1 private static readonly ContextCallback s_invokeContextCallback = delegate(object state) // GetInvokeActionCallback方法的实现 2 { 3 ((Action)state)(); 4 }; 5 6 protected void RunCallback(ContextCallback callback, object state, ref Task currentTask) 7 { 8 Task task = currentTask; 9 try 10 { 11 if (task != null) 12 { 13 currentTask = null; 14 } 15 ExecutionContext capturedContext = m_capturedContext; 16 if (capturedContext == null) 17 { 18 callback(state); // 回调 19 } 20 else 21 { 22 ExecutionContext.RunInternal(capturedContext, callback, state); 23 } 24 } 25 catch (Exception exception) 26 { 27 Task.ThrowAsync(exception, null); 28 } 29 finally 30 { 31 if (task != null) 32 { 33 currentTask = task; 34 } 35 } 36 }
整个逻辑走到这,算基本走完了吧.如果不需要线程上下文切换,直接回调 AsyncStateMachineBox .moveNext.Action,由于它绑定到了 AsyncStateMachineBox .MoveNext,而 AsyncStateMachineBox.moveNext里面直接调用了 StateMachine.MoveNext(),这时我们的状态机movenext发生第二次调用并且继续执行await后面的逻辑.这里我简单贴一下AsyncStateMachineBox代码,它派生自 IAsyncStateMachineBox,它的初始化是在等待状态机状态变化时初始化.
1 private class AsyncStateMachineBox<TStateMachine> : Task<TResult>, IAsyncStateMachineBox where TStateMachine : notnull, IAsyncStateMachine 2 { 3 4 public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext)); 5 6 7 public void MoveNext() 8 { 9 MoveNext(null); 10 } 11 其他成员.... 12 13 private void MoveNext(Thread threadPoolThread) 14 { 15 // 其他代码.... 16 ExecutionContext context = Context; 17 if (context == null) 18 { 19 StateMachine.MoveNext(); 20 } 21 else if (threadPoolThread == null) 22 { 23 ExecutionContext.RunInternal(context, s_callback, this); 24 } 25 else 26 { 27 ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, context, s_callback, this); 28 } 29 } 30 }
最后,接着执行我们还原的那部分movenext代码,只不过是else部分.简单总结一下,在movenext调用堆栈里面有几个await就会有几个状态,第一次-1状态,获取awaiter对象,如果async方法里面有开启新线程,此时主线程会返回执行主线程自己的逻辑,接着判断任务是否完成,如果未完成,调用AwaitUnsafeOnCompleted方法,在该方法里面hook状态机状态,如果状态发生变更,封装状态机和awaiter以及线程上下文并且继续回调我们的movenext方法,此时如果movenext里面只有一个await,表示任务已完成,直接从awaiter里面获取结果,这里有个地方需要注意,如果需要线程上下文切换,会调用context的post方法完成.
1 else{ 2 awaiter = <>u__1; 3 <>u__1 = default(TaskAwaiter<string>); 4 num = (<>1__state = -1); 5 <>s__2 = awaiter.GetResult(); 6 7 8 <r1>5__1 = <>s__2; 9 <>s__2 = null; 10 Console.WriteLine(<r1>5__1); 11 Console.WriteLine("end complete"); 12 return; 13 }
感觉异步编程都是围绕Task这个对象在开展,也走读了task源码,实现还是比较复杂,好了就到这.以上仅供参考.