async-await原理解析
阅读原文时间:2023年07月15日阅读:1

在用async包裹的方法体中,可以使用await关键字以同步的方式编写异步调用的代码。那么它的内部实现原理是什么样的呢?我们是否可以自定义await以实现定制性的需求呢?先来看一个简单的例子:

 class Test {  
     public static void Main (string\[\] args) {  
         Task.Run (new Func<Task<string>>(task1));  
         Console.ReadLine ();  
     }

     private async static Task<string> task1() {  
         string ret = await task2 ();  
         Console.WriteLine ("Await Task Result:" + ret);  
         return ret;  
     }

     private static Task<string> task2() {  
         return Task.FromResult<string> ("Task2");  
     }  
 }

通过ILSpy反编译(要关闭"视图-选项-反编译await/async"菜单项),得到如下代码:

 internal class Test  
 {  
     \[CompilerGenerated\]  
     \[StructLayout(LayoutKind.Auto)\]  
     private struct <task1>d\_\_0 : IAsyncStateMachine  
     {  
         public int <>1\_\_state;  
         public AsyncTaskMethodBuilder<string> <>t\_\_builder;  
         public string <ret>5\_\_1;  
         private TaskAwaiter<string> <>u\_\_$awaiter2;  
         private object <>t\_\_stack;

         void IAsyncStateMachine.MoveNext()  
         {  
             string result;  
             try  
             {  
                 int num = this.<>1\_\_state;  
                 if (num != -)  
                 {  
                     TaskAwaiter<string> taskAwaiter;  
                     if (num != )  
                     {  
                         taskAwaiter = Test.task2().GetAwaiter();  
                         if (!taskAwaiter.IsCompleted)  
                         {  
                             this.<>1\_\_state = ;  
                             this.<>u\_\_$awaiter2 = taskAwaiter;  
                             this.<>t\_\_builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Test.<task1>d\_\_0>(ref taskAwaiter, ref this);  
                             return;  
                         }  
                     }  
                     else  
                     {  
                         taskAwaiter = this.<>u\_\_$awaiter2;  
                         this.<>u\_\_$awaiter2 = default(TaskAwaiter<string>);  
                         this.<>1\_\_state = -;  
                     }  
                     string arg\_86\_0 = taskAwaiter.GetResult();  
                     taskAwaiter = default(TaskAwaiter<string>);  
                     string text = arg\_86\_0;  
                     this.<ret>5\_\_1 = text;  
                     Console.WriteLine("Await Task Result:" + this.<ret>5\_\_1);  
                     result = this.<ret>5\_\_1;  
                 }  
             }  
             catch (Exception exception)  
             {  
                 this.<>1\_\_state = -;  
                 this.<>t\_\_builder.SetException(exception);  
                 return;  
             }  
             this.<>1\_\_state = -;  
             this.<>t\_\_builder.SetResult(result);  
         }

         \[DebuggerHidden\]  
         void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)  
         {  
             this.<>t\_\_builder.SetStateMachine(param0);  
         }  
     }

     public static void Main(string\[\] args)  
     {  
         Task.Run<string>(new Func<Task<string>>(Test.task1));  
         Console.ReadLine();  
     }

     \[DebuggerStepThrough, AsyncStateMachine(typeof(Test.<task1>d\_\_0))\]  
     private static Task<string> task1()  
     {  
         Test.<task1>d\_\_0 <task1>d\_\_;  
         <task1>d\_\_.<>t\_\_builder = AsyncTaskMethodBuilder<string>.Create();  
         <task1>d\_\_.<>1\_\_state = -;  
         AsyncTaskMethodBuilder<string> <>t\_\_builder = <task1>d\_\_.<>t\_\_builder;  
         <>t\_\_builder.Start<Test.<task1>d\_\_0>(ref <task1>d\_\_);  
         return <task1>d\_\_.<>t\_\_builder.Task;  
     }

     private static Task<string> task2()  
     {  
         return Task.FromResult<string>("Task2");  
     }  
 }

按照代码的调用顺序,我们关注下task1()的内部实现。

首先是初始化结构体d_0的实例d__。那么d__0是个什么东东呢?由编译器的生成代码中可以看到,它是一个实现了IAsyncStateMachine接口的结构体,而用户代码则被编译器重新组织进了MoveNext()方法中。d__0有个内部状态成员<>1__state,MoveNext()方法根据这个状态调转到相应的代码块中加以执行。

了解了d__0的声明实现,再看下task1()方法中的具体调用。在创建实例d__之后,设置初始状态<>1__state为-1,并调用<>t__builder的Start方法。不难推断,在Start方法中会调用d__.MoveNext(),此时内部状态为-1,会先调用Test.task2().GetAwaiter()获取其所关联的TaskAwaiter实例。如果awaiter当前是未结束的话,则设置<>1__state为0,并将当前d__作为参数关联到TaskAwaiter实例的onCompletedContinuation回调延续中去。当未来某个时刻,TaskAwaiter所关联的Task任务结束时,会设置awaiter的异步结果并触发回调延续,导致调用d__.MoveNext()方法,并最终跳转到用户代码块中,获取awaiter的异步结果并交由用户代码处理。这个回调,基于Task.ConfigureAwait(true/false)的不同,会在后续切换到当前线程或是从线程池中取了一个空闲线程来处理(更细节可参考.net源码分析)。

这里要顺便提一句,在本例中,通过Task.Run创建了taskX1,await之后的代码与taskX1没有任何关系,从编译器生成的代码来看,在调用task1()方法并调用d__.Start()方法之后taskX便结束了,虽然task1()方法返回了新的Task实例,但是只是特定类型的返回值而已,与taskX1或Task没有任何关系。

由以上分析可以看到,async/await只是一个语法糖,async告知编译器要生成状态机代码,await则是配合生成GetAwaiter(),并封装跳转的用户代码块。除此之外,async/await与Task没有任何直接关系。而TaskAwaiter的作用,是实现INotifyCompletion(在System.Runtime.CompilerServices命名空间)以桥接异步回调过程。那么第二个自定义await的问题便一目了然了:任何类型,只需要实现GetAwaiter()方法以返回INotifyCompletion实例,便可以被await。

举个例子:

 class TestAwaiter<T> : INotifyCompletion {  
     private T result;  
     private Action continuation;

     // INotifyCompletion Implement  
     public void OnCompleted(Action continuation) { this.continuation = continuation; }

     // Compiler Call Methods  
     public bool IsCompleted { get; private set; }  
     public T GetResult() { return result; }  
     public TestAwaiter<T> GetAwaiter() { return this; }

       // Self Call Methods  
     public void SetResult(T ret) {  
         result = ret;  
         if (continuation != null) {  
             continuation ();  
         }  
     }  
 }

 class Test {  
     public static void Main (string\[\] args) {  
         Task.Run (new Action(task1));  
         Console.ReadLine ();  
     }

     private async static void task1() {  
         Console.WriteLine ("Begin await:");  
         int ret = await testAwaiter ();  
         Console.WriteLine ("Await Task Result:" + ret);  
     }

     private static TestAwaiter<int> testAwaiter() {  
         TestAwaiter<int> awaiter = new TestAwaiter<int> ();  
         ThreadPool.QueueUserWorkItem (\_ => {  
             Thread.Sleep();  
             awaiter.SetResult ();  
         });  
         return awaiter;  
     }  
 }

这里没有再定义单独的类型以返回TestAwaiter,而是把二者都封装在了TestAwaiter内部。运行结果如下:

Begin await:

Await Task Result:100

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器