进阶篇:以IL为剑,直指async/await

本文从IL角度深入剖析async/await的工作原理。async/await是.NET 4.5引入的特性,用于简化异步编程。编译时,它们会被转换为状态机,通过IAsyncStateMachine接口进行操作。文章通过示例代码展示了如何使用ildasm查看编译后的IL,解释了如何创建状态机、启动异步操作,以及AsyncVoidMethodBuilder在异常处理中的作用。async/await的本质是一个语法糖,不创建新线程,而是通过SynchronizationContext进行线程切换,决定在哪个线程上执行后续代码。配置awaitable的行为可通过Task.ConfigureAwait来控制,影响异步操作完成后是否回到原来的线程执行。最后,文章总结了async/await的关键点,包括编译器如何改写代码逻辑,以及异常处理机制。
摘要由CSDN通过智能技术生成

接上篇: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/'<Displ
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值