简单说说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源码,实现还是比较复杂,好了就到这.以上仅供参考.
posted @ 2020-06-11 15:09  小菜 。  阅读(1509)  评论(1编辑  收藏  举报