接上篇: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