一:背景

1. 讲故事

await,async 这玩意的知识点已经被人说的烂的不能再烂了,看似没什么好说的,但我发现有不少文章还是从理论上讲述了这两个语法糖的用法,懂得还是懂,不懂的看似懂了过几天又不懂了,人生如戏全靠记是不行的哈,其实本质上来说 await, async 只是编译器层面上的语法糖,在 IL 层面都会被打成原型的,所以在这个层面上认识这两个语法糖是非常有必要的。

二:从 IL 层面认识

1. 使用 WebClient 下载

为了方便打回原型,我先上一个例子,使用 webclient 异步下载 http://cnblogs.com 的html,代码如下:


   class Program
   {

       static void Main(string[] args)
       
{
           var html = GetResult();

           Console.WriteLine("稍等... 正在下载 cnblogs -> html \r\n");

           var content = html.Result;

           Console.WriteLine(content);
       }

       static async Task<string> GetResult()
       {
           var client = new WebClient();

           var content = await client.DownloadStringTaskAsync(new Uri("http://cnblogs.com"));

           return content;
       }
   }

上面的代码非常简单,可以看到异步操作没有阻塞主线程输出: 稍等... 正在下载 cnblogs -> html \r\n, 编译器层面没什么好说的 ,接下来看下在 IL 层面发生了什么?

2. 挖掘 await async 的IL代码

还是老规矩, ilSpy 走起,如下图:

可以看到,这里有一个 GetResult 方法 ,一个 Main 方法,还有一个不知道在哪里冒出来的 <GetResult>d__1 类,接下来和大家一个一个聊。

<1 style="box-sizing: border-box;"> \d__1> 类

因为不知道从哪里冒出来的,特别引人关注,所以看看它的 IL 是咋样的?


.class nested private auto ansi sealed beforefieldinit '<GetResult>d__1'
   extends [System.Runtime]System.Object
   implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
{
   .method private final hidebysig newslot virtual
       instance
void MoveNext () cil managed
   
{
   }

   .method private final hidebysig newslot virtual
       instance
void SetStateMachine (
           
class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
       )
cil managed
   
{

   }
}

从上面的 IL 代码可以看到,这是自动生成的 <GetResult>d__1 类实现了接口 IAsyncStateMachine,定义如下:

看到里面的 MoveNext 是不是很眼熟,平时你在 foreach 集合的时候就会用到这个方法,那时人家叫做枚举类,在这里算是被改造了一下, 叫状态机。

<2 style="box-sizing: border-box;"> GetResult ()

为了方便演示,我对方法体中的 IL 代码做一下简化:


.method private hidebysig static
   class [System.Runtime]System.Threading.Tasks.Task`1<string> GetResult () cil managed
{

   IL_0000: newobj instance void ConsoleApp3.Program/'<GetResult>d__1'::.ctor()
   IL_0005: stloc.0
   IL_0006: ldloc.0
   IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Create()
   IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'
   IL_0011: ldloc.0
   IL_0012: ldc.i4.m1
   IL_0013: stfld int32 ConsoleApp3.Program/'<GetResult>d__1'::'<>1__state'
   IL_0018: ldloc.0
   IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'
   IL_001e: ldloca.s 0
   IL_0020: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Start<class ConsoleApp3.Program/'<GetResult>d__1'>(!!0&)
   IL_0025:
ldloc.0
   IL_0026: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1'::'<>t__builder'
   IL_002b: call instance class [System.Runtime]System.Threading.Tasks.Task`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::get_Task()
   IL_0030: ret
} // end of method Program::GetResult

如果你稍微懂一点的话,在 IL_0000 处的 newobj 你就应该知道这个方法就是做了 new <GetResult>d__1,然后从 IL_002b 处返回了一个 get_Task() ,这时候你就应该明白,为什么主线程不会被阻塞,因为人家返回的是 Task<string> ,对吧,最后的 http 结果会藏在 Task<string> 中,这样是不是就很好理解了。

<3 style="box-sizing: border-box;"> Main

Main方法没有做任何改变,原来是什么样现在还是什么样。

三:将 IL 代码 回写为 C#

1. 完整 C# 代码

通过前面一部分你应该对 await ,async 在 IL 层面有了一个框架性的认识,这里我就全部反写成 C# 代码:


   class Program
   {

       static void Main(string[] args)
       
{
           var html = GetResult();

           Console.WriteLine("稍等... 正在下载 cnblogs -> html \r\n");

           var content = html.Result;

           Console.WriteLine(content);
       }

       static Task<string> GetResult()
       {
           GetResult stateMachine = new GetResult();

           stateMachine.builder = AsyncTaskMethodBuilder<string>.Create();

           stateMachine.state = -1;

           stateMachine.builder.Start(ref stateMachine);

           return stateMachine.builder.Task;
       }
   }

   class GetResult : IAsyncStateMachine
   {
       public int state;
       public AsyncTaskMethodBuilder<string> builder;
       private WebClient client;
       private string content;
       private string s3;
       private TaskAwaiter<string> awaiter;

       public void MoveNext()
       
{
           var result = string.Empty;
           TaskAwaiter<string> localAwaiter;
           GetResult stateMachine;

           int num = state;

           try
           {
               if (num == 0)
               {
                   localAwaiter = awaiter;
                   awaiter = default(TaskAwaiter<string>);
                   num = state = -1;
               }
               else
               {
                   client = new WebClient();

                   localAwaiter = client.DownloadStringTaskAsync(new Uri("http://cnblogs.com")).GetAwaiter();

                   if (!localAwaiter.IsCompleted)
                   {
                       num = state = 0;
                       awaiter = localAwaiter;
                       stateMachine = this;
                       builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine);
                       return;
                   }
               }

               s3 = localAwaiter.GetResult();
               content = s3;
               s3 = null;
               result = content;
           }
           catch (Exception exx)
           {
               state = -2;
               client = null;
               content = null;
               builder.SetException(exx);
           }

           state = -2;
           client = null;
           content = null;
           builder.SetResult(result);
       }

       public void SetStateMachine(IAsyncStateMachine stateMachine) { }
   }

可以看到,回写成 C# 代码之后跑起来是没有任何问题的,为了方便理解,我先来画一张流程图。

通过上面的 xmind,它基本流程就是: stateMachine.builder.Start(ref stateMachine) -> GetResult.MoveNext -> client.DownloadStringTaskAsync -> localAwaiter.IsCompleted = false -> builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine) -> GetResult.MoveNext -> localAwaiter.GetResult() -> builder.SetResult(result)

2. 剖析 AsyncTaskMethodBuilder

其实你仔细观察会发现,所谓的 await,async 的异步化运作都是由 AsyncTaskMethodBuilder 承载的,如异步任务的启动,对html结果的封送,接触底层IO,其中 Task<string> 对应着 AsyncTaskMethodBuilder<string>, Task 对应着 AsyncTaskMethodBuilder, 这也是为什么编译器在 async 处一直提示你返回 Task 和 Task<string>,如果不这样的话的就找不到对应 AsyncTaskMethodBuilder 了,对吧,如下图:

然后着重看下 AwaitUnsafeOnCompleted 方法,这个方法非常重要,其注释如下:


       //
       // Summary:
       //     Schedules the state machine to proceed to the next action when the specified
       //     awaiter completes. This method can be called from partially trusted code.
       public void AwaitUnsafeOnCompleted<[NullableAttribute(0)] TAwaiter, [NullableAttribute(0)] TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
           where TAwaiter : ICriticalNotifyCompletion
           where TStateMachine : IAsyncStateMachine;

一旦调用了这个方法,就需要等待 底层IO 将任务处理完毕之后二次回调 GetResult.MoveNext,也就表示要么异常要么完成任务, Awaiter 包装的 Task 结果封送到 builder.SetResult

然后简单说一下 状态机 的走法,通过调试会发现这里会走 两次 MoveNext,一次启动,一次拿结果。

<1> 第一次回调 MoveNext

第一次 MoveNext 的触发由 stateMachine.builder.Start(ref stateMachine) 发起,可以用 dnspy 去调试一下,如下图:

<2> 第二次回调 MoveNext

第二次 MoveNext 的触发由 builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine) 开始,可以看到一旦 网络驱动程序 处理完毕后就由线程池IO线程主动发起到最后触发代码中的 MoveNext,最后就是到 awaiter 中获取 task 的 result 处结束,如下图:

四:总结

语法糖有简单和复杂之分,复杂的也不要怕,学会将 IL 代码翻译成 C# ,或许你以前很多不明白的地方此时都会豁然开朗,不是吗?


©著作权归作者所有:来自51CTO博客作者mb5fd86a704dffe的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 遍历 Dictionary,你会几种方式?
  2. 用过 mongodb 吧, 这三个大坑踩过吗?
  3. Enumerable 下又有新的扩展方法啦,快来一睹为快吧
  4. 工作十余年,还是一直被问 委托和事件 有什么区别? 真是够了
  5. 我的第23个代码
  6. 我的第24个代码
  7. PHP类成员重载与命名空间
  8. 多线程基础知识
  9. centos 将本地代码软链到web服务代码目录

随机推荐

  1. Android获取运营商代码
  2. Android(安卓)中如何复制文件的操作
  3. android与服务端通信
  4. Activity-Spinner使用
  5. android RecycleView实现下拉刷新和上拉
  6. Android Studio 报No resource found tha
  7. 比比看,Android和Mango到底有什么不同?
  8. Android 开发集锦
  9. android activity的生命周期,四种启动模
  10. android 开发时出现 Please ensure that