< 返回技术文档列表

如何理解Await与Async

发布时间:2021-11-07 02:52:02

这篇文章主要介绍“如何理解Await与Async ”,在日常操作中,相信很多人在如何理解Await与Async 问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何理解Await与Async ”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一:背景

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;        }    }

如何理解Await与Async

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

2. 挖掘 await async 的IL代码

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

如何理解Await与Async

可以看到,这里有一个 GetResult 方法 ,一个 Main 方法,还有一个不知道在哪里冒出来的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 代码可以看到,这是自动生成的d__1 类实现了接口 IAsyncStateMachine,定义如下:

如何理解Await与Async

看到里面的 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 你就应该知道这个方法就是做了 newd__1,然后从 IL_002b  处返回了一个 get_Task() ,这时候你就应该明白,为什么主线程不会被阻塞,因为人家返回的是 Task,对吧,最后的 http  结果会藏在 Task中,这样是不是就很好理解了。

<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) { }     }

如何理解Await与Async

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

如何理解Await与Async

通过上面的 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对应着  AsyncTaskMethodBuilder, Task 对应着 AsyncTaskMethodBuilder, 这也是为什么编译器在  async 处一直提示你返回 Task 和 Task,如果不这样的话的就找不到对应 AsyncTaskMethodBuilder  了,对吧,如下图:

如何理解Await与Async

然后着重看下 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  去调试一下,如下图:

如何理解Await与Async

<2> 第二次回调 MoveNext

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

如何理解Await与Async

到此,关于“如何理解Await与Async ”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注血鸟云网站,小编会继续努力为大家带来更多实用的文章!


/template/Home/Zkeys/PC/Static