上一篇讲了这么多,其实说的就是一个事,return会被编译器重写成SetResult,所以如果我们的异步函数返回的是一个Task<int>,代码就要改成这样:

  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using System.Threading.Tasks;
  4.  
  5. namespace StateMachineDemo
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. Console.WriteLine(FooAsync2().Result);
  12. }
  13.  
  14. public static async Task&lt;int&gt; FooAsync()
  15. {
  16. await Task.Delay(10000);
  17. return 42;
  18. }
  19.  
  20. public static Task&lt;int&gt; FooAsync2()
  21. {
  22. var stateMachine = new FooAsyncStateMachine();
  23. stateMachine.MethodBuilder = new AsyncTaskMethodBuilder&lt;int&gt;();
  24.  
  25. stateMachine.MethodBuilder.Start(ref stateMachine);
  26.  
  27. return stateMachine.MethodBuilder.Task;
  28. }
  29.  
  30. public struct FooAsyncStateMachine : IAsyncStateMachine
  31. {
  32. public AsyncTaskMethodBuilder&lt;int&gt; MethodBuilder;
  33. public void MoveNext()
  34. {
  35. MethodBuilder.SetResult(42);
  36. }
  37.  
  38. public void SetStateMachine(IAsyncStateMachine stateMachine)
  39. {
  40. throw new NotImplementedException();
  41. }
  42. }
  43. }
  44. }

函数的返回值换成了Task<int>,里面返回MethodBuilder.Task,原来的AsyncVoidMethodBuilder对应换成了AsyncTaskMethodBuilder<int>,SetResult这里面return了一个值,就是这样。

下面来加点料,前面说的都是平凡的异步方法,现在我们真正的来在方法里面await一个东西,比如await Task.Delay(10000);

  1. public static async Task<int> FooAsync()
  2. {
  3. await Task.Delay(10000);
  4. return 42;
  5. }

这个时候状态机就要真正的起作用了,这个代码有两个状态——await执行之前到开始执行、await执行之后,也就是说对于一个有n个await函数,他就有n+1个状态,分别是从开始到第1个await,第i到i+1个await,第n个await到结束。

对于我们的代码,不妨用一个int来表示状态,0是第一个状态,1是第二个状态,我们先在MoveNext里面把框架写好:

  1. public struct FooAsyncStateMachine : IAsyncStateMachine
  2. {
  3. public AsyncTaskMethodBuilder<int> MethodBuilder;
  4.  
  5. public int State;
  6.  
  7. public void MoveNext()
  8. {
  9. if (State == 0)
  10. {
  11. //await之前以及开始await
  12. return;
  13. }
  14.  
  15. if (State == 1)
  16. {
  17. //await之后
  18. MethodBuilder.SetResult(42);
  19. return;
  20. }
  21. }
  22.  
  23. public void SetStateMachine(IAsyncStateMachine stateMachine)
  24. {
  25. throw new NotImplementedException();
  26. }
  27. }

好了现在看State==0的时候,这里面await之前没有代码,所以我们只需要启动Task.Delay(10000),首先是Task.Delay(10000);表达式的求值:

  1. public void MoveNext()
  2. {
  3. if (State == 0)
  4. {
  5. Task.Delay(10000);
  6. return;
  7. }
  8.  
  9. if (State == 1)
  10. {
  11. //await之后
  12. MethodBuilder.SetResult(42);
  13. return;
  14. }
  15. }

这样还不行,我们没法确定Task.Delay什么时候完成,编译器在这里是这样实现的:Task.Delay(10000);返回的是一个Task,对他调用GetAwaiter,拿到awaiter:

  1. public void MoveNext()
  2. {
  3. if (State == 0)
  4. {
  5. TaskAwaiter awaiter = Task.Delay(10000).GetAwaiter();
  6. //注册回调什么的
  7. return;
  8. }
  9.  
  10. if (State == 1)
  11. {
  12. //await之后
  13. MethodBuilder.SetResult(42);
  14. return;
  15. }
  16. }

如果我们直接这么写

  1. if (State == 0)
  2. {
  3. TaskAwaiter awaiter = Task.Delay(10000).GetAwaiter();
  4. awaiter.GetResult();
  5. return;
  6. }

GetResult是个阻塞调用,卡住控制流就不好了,所以GetResult要在Task确定完成的时候,也就是State==1的时候(State被谁设为1我们之后讲),也就是在第二个if里面,GetResult

  1. if (State == 1)
  2. {
  3. //await之后
  4. awaiter.GetResult();
  5. MethodBuilder.SetResult(42);
  6. return;
  7. }

然而你会发现TaskAwaiter的作用域是前一个if块,在这里我们访问不到,所以TaskAwaiter也应该作为类成员而不是方法内的局部变量,顺便说这东西也是一个struct,同样是我们不希望有额外的堆操作开销,最后改好的StateMachine是这样

  1. public struct FooAsyncStateMachine : IAsyncStateMachine
  2. {
  3. public AsyncTaskMethodBuilder<int> MethodBuilder;
  4.  
  5. public int State;
  6.  
  7. private TaskAwaiter awaiter;
  8.  
  9. public void MoveNext()
  10. {
  11. if (State == 0)
  12. {
  13. awaiter = Task.Delay(10000).GetAwaiter();
  14. return;
  15. }
  16.  
  17. if (State == 1)
  18. {
  19. //await之后
  20. awaiter.GetResult();
  21. MethodBuilder.SetResult(42);
  22. return;
  23. }
  24. }
  25.  
  26. public void SetStateMachine(IAsyncStateMachine stateMachine)
  27. {
  28. throw new NotImplementedException();
  29. }
  30. }

还没完,第一个if里面是已经让awaiter跑起来了,还需要一个机制让它在完成的时候State变成1,以及让他调用MoveNext,走第二个if,继续后面的控制流。这里有一个简单的优化,我们先检测task有没有完成,如果完成了,那么把State设为1,直接goto到第二个if,如果没有的话,再注册让他完成时调用MoveNext的相关东西:

  1. if (State == 0)
  2. {
  3. awaiter = Task.Delay(10000).GetAwaiter();
  4. if (awaiter.IsCompleted)
  5. {
  6. State = 1;
  7. goto state1;
  8. }
  9. else
  10. {
  11. //task还没完
  12. }
  13. return;
  14. }
  15. state1:
  16. if (State == 1)
  17. {
  18. //await之后
  19. awaiter.GetResult();
  20. MethodBuilder.SetResult(42);
  21. return;
  22. }
  23. }

如果task还没完,我们同样需要把State设为1,这样当然完了的时候,一个什么东西继续调用MoveNext,就能直接执行第二个if块的内容了。关于注册完成时调用MoveNext,这里有个API函数叫做

  1. if (State == 0)
  2. {
  3. awaiter = Task.Delay(10000).GetAwaiter();
  4. if (awaiter.IsCompleted)
  5. {
  6. State = 1;
  7. goto state1;
  8. }
  9. else
  10. {
  11. State = 1;
  12. MethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
  13. }
  14. return;
  15. }

StateMachine.AwaitUnsafeOnCompleted,还有一个函数叫AwaitOnCompleted,具体里面有什么差别我也不是很懂,总之编译器重写的时候用的是这个,两个参数,一个是awaiter,要等待完成的东西,一个this,完成之后调用this的MoveNext,逻辑很清楚,可以看到这里用了ref,为了把参数都转移到堆上,因为之后方法就返回栈就回退了,栈上的东西都会销毁。

看似什么都已经好了(其实并没有),我们运行一下看看:

哎,挂在了这里,这里我们应该写成这样:

  1. public void SetStateMachine(IAsyncStateMachine stateMachine)
  2. {
  3. MethodBuilder.SetStateMachine(stateMachine);
  4. }

为什么一个method builder要SetStateMachine呢,回去看我们当初写的代码:

  1. public static Task<int> FooAsync2()
  2. {
  3. var stateMachine = new FooAsyncStateMachine();
  4. stateMachine.MethodBuilder = new AsyncTaskMethodBuilder<int>();
  5.  
  6. stateMachine.MethodBuilder.Start(ref stateMachine);
  7.  
  8. return stateMachine.MethodBuilder.Task;
  9. }

这里MethodBuilder是获得了一个ref,引用到到栈上的stateMachine,C#的ref你可以理解为指针:最开始,MethodBuilder里面有一个指向栈上stateMachine的指针,这很好,但是前面一步我们用MethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref this); 调用把state machine本体转移到了堆上,所以原来MethodBuilder里面指向栈上StateMachine的引用(指针)就无效了,所以我们需要给MethodBuilder一个新的堆上的stateMachine的引用,让他持有正确的状态机。

现在都好了,我在if(State==1)那里下个断点,我们跑一下代码,看看回调是怎么发生的:

代码停在了这里,看调用堆栈,是另一条独立的线程继续调用了MoveNext,从Delay产生作用的TimerCallback一直向上,最后调用了MoveNext,值得一提的是,主线程被某种同步机制卡住了,因为那时候FooAsync2的结果还没有return出来给它打印,看看并行堆栈监视

的确是一条独立的线程调用了MoveNext,主线程也被卡住了。

总结一下,编译器会把第i个await重写为两个部分,一个是在状态i里面的GetAwaiter、测试IsCompleted,有的话直接跳第i+1个状态、没有的话注册回调,另一个是在状态i+1里面的GetResult。

就这么多,后面复杂的东西,比如AwaitUnsafeOnComplete为什么Unsafe,以及主线程被卡住的机制,我还不怎么懂,这篇主要是讲一下编译器怎么重写最简单的await,大概思路是这样,至于更复杂的await,比如if块里的,try-catch里的,不是很懂也就不说了。

Async Programming - 1 async-await 糖的本质(2)的更多相关文章

  1. Async Programming - 1 async-await 糖的本质(1)

    这一个系列的文章主要来讲 C# 中的语言特性 async-await 在语言层面的本质,我们都知道 await 是编译器进行了一个 rewrite,然而这个 rewrite 并不是直接 rewrite ...

  2. C#的多线程——使用async和await来完成异步编程(Asynchronous Programming with async and await)

    https://msdn.microsoft.com/zh-cn/library/mt674882.aspx 侵删 更新于:2015年6月20日 欲获得最新的Visual Studio 2017 RC ...

  3. Asynchronous programming with async and await (C#)

    Asynchronous Programming with async and await (C#) | Microsoft Docs https://docs.microsoft.com/en-us ...

  4. Async Programming All in One

    Async Programming All in One Async & Await Frontend (async () => { const url = "https:// ...

  5. [转] Scala Async 库 (Scala future, await, async)

    [From] https://colobu.com/2016/02/15/Scala-Async/ 在我以前的文章中,我介绍了Scala Future and Promise.Future代表一个异步 ...

  6. async await 的 实质 本质

    async await  的 实质 就是 用 “状态机” 来 取代 函数层层调用 . async await  的 本质 是 语法糖,  和 提高性能 什么的 没什么关系 . 为了避免理解歧义, 我把 ...

  7. Javascript 使用 async 声明符和 await 操作符进行异步操作

    async function 声明用于定义一个返回 AsyncFunction 对象的异步函数 await  操作符用于等待一个Promise 对象.它只能在异步函数 async function 中 ...

  8. a kind of async programming in c#, need to reference definition

    void Main() { Run d=new Run(RunHandler); IAsyncResult result= d.BeginInvoke(new AsyncCallback(CallBa ...

  9. [Javascript] Await a JavaScript Promise in an async Function with the await Operator

    The await operator is used to wait for a promise to settle. It pauses the execution of an async func ...

随机推荐

  1. Weibo SDK WP版本回调参数没有uid的解决方法

    服务端跟新浪微博交互的时候需要用到UID参数, 但WP的WeiboSDK默认没有提供, 只要增加一个类成员就好了, 序列化json的时候程序会自动处理 下载SDK源代码http://weibowp7s ...

  2. php策略模式

    一.编写一个简单的网页计算器功能 代码片段: 视图页面(两个输入框,一个下拉列表选择操作符,一个计算按钮) 后台php程序处理(最原始的写法) 假如新加一个运算方式(取余),那么就得修改php后台程序 ...

  3. android 数据下载 工具类

    传入图片地址,获得服务器返回的流. 把流转化为byte[]数组

  4. Favorite Setting

    1. You Tube download Opera plugin:Video Downloader Pro Website:http://en.savefrom.net 2.

  5. 页面无法加载main.css

    html应该用append,不是html,会覆盖掉文件

  6. String、Stringbuffer、StringBuilder的区别(转载)

    最近学习到StringBuffer,心中有好些疑问,搜索了一些关于String,StringBuffer,StringBuilder的东西,现在整理一下. 关于这三个类在字符串处理中的位置不言而喻,那 ...

  7. YII2的增删改查

    insert into table (field1,field2)values('1','2');delete from table where   condition update  table s ...

  8. js 获取小数点位数方法及 字符串与数字之间相互转换方法

    1.获取小数点位数方法 a. 使用 js 中 subsrting,indexOf,parseFloat三个函数,代码如下: var s = "22.127456" ;//s 为 字 ...

  9. python线程池(threadpool)模块使用笔记

    一.安装与简介 pip install threadpool pool = ThreadPool(poolsize) requests = makeRequests(some_callable, li ...

  10. centos 6 cglib

    Error: Package: glibc-2.12-1.166.el6_7.3.i686 (@ultra-centos-6.7-updates) Requires: glibc-common = 2 ...