考虑到直接讲实现一个类Task库思维有点跳跃,所以本节主要讲解Async/Await的本质作用(解决了什么问题),以及Async/Await的工作原理。实现一个类Task的库则放在后面讲。首先回顾一下上篇博客的场景。

  1. class Program
  2. {
  3. public static string GetMessage()
  4. {
  5. return Console.ReadLine();
  6. }
  7. public static string TranslateMessage(string msg)
  8. {
  9. return msg;
  10. }
  11. public static void DispatherMessage(string msg)
  12. {
  13. switch (msg)
  14. {
  15. case "MOUSE_MOVE":
  16. {
  17. OnMOUSE_MOVE(msg);
  18. break;
  19. }
  20. case "MOUSE_DOWN":
  21. {
  22. OnMouse_DOWN(msg);
  23. break;
  24. }
  25. default:
  26. break;
  27. }
  28. }
  29. public static void OnMOUSE_MOVE(string msg)
  30. {
  31. Console.WriteLine("开始绘制鼠标形状");
  32. }
  33. public static int Http()
  34. {
  35. Thread.Sleep(1000);//模拟网络IO延时
  36. return 1;
  37. }
  38. public static void HttpAsync(Action<int> action,Action error)
  39. {
  40. //这里我们用另一个线程来实现异步IO,由于Http方法内部是通过Sleep来模拟网络IO延时的,这里也只能通过另一个线程来实现异步IO
  41. //但记住,多线程是实现异步IO的一个手段而已,它不是必须的,后面会讲到如何通过一个线程来实现异步IO。
  42. Thread thread = new Thread(() =>
  43. {
  44. try
  45. {
  46. int res = Http();
  47. action(res);
  48. }
  49. catch
  50. {
  51. error();
  52. }
  53. });
  54. thread.Start();
  55. }
  56. public static Task<int> HttpAsync()
  57. {
  58. return Task.Run(() =>
  59. {
  60. return Http();
  61. });
  62. }
  63. public static void OnMouse_DOWN(string msg)
  64. {
  65. HttpAsync()
  66. .ContinueWith(t =>
  67. {
  68. if(t.Status == TaskStatus.Faulted)
  69. {
  70. }else if(t.Status == TaskStatus.RanToCompletion)
  71. {
  72. Console.WriteLine(1);
  73. //做一些工作
  74. }
  75. })
  76. .ContinueWith(t =>
  77. {
  78. if (t.Status == TaskStatus.Faulted)
  79. {
  80. }
  81. else if (t.Status == TaskStatus.RanToCompletion)
  82. {
  83. Console.WriteLine(2);
  84. //做一些工作
  85. }
  86. })
  87. .ContinueWith(t =>
  88. {
  89. if (t.Status == TaskStatus.Faulted)
  90. {
  91. }
  92. else if (t.Status == TaskStatus.RanToCompletion)
  93. {
  94. Console.WriteLine(3);
  95. //做一些工作
  96. }
  97. });
  98. }
  99. static void Main(string[] args)
  100. {
  101. while (true)
  102. {
  103. string msg = GetMessage();
  104. if (msg == "quit") return;
  105. string m = TranslateMessage(msg);
  106. DispatherMessage(m);
  107. }
  108. }
  109. }

  在OnMouse_DOWN这个处理函数中,我们使用Task的ContinueWith函数进行链式操作,解决了回调地狱问题,但是总感觉有点那么不爽,我们假想有个关键字await它能实现以下作用:首先await必须是Task类型,必须是Task类型的(其实不是必要条件,后面会讲到)原因是保证必须有ContinueWith这个函数,如果Task没有返回值,则把await后面的代码放到Task中的ContinueWith函数体内,如果有返回值,则把Await后的结果转化为访问Task.Result属性,文字说的可能不明白,看下示例代码

  1. //无返回值转换前
  2. public async void Example()
  3. {
  4. Task t = Task.Run(() =>
  5. {
  6. Thread.Sleep(1000);
  7. });
  8. await t;
  9. //做一些工作
  10. }
  11. //无返回值转换后
  12. public void Example()
  13. {
  14. Task t = Task.Run(() =>
  15. {
  16. Thread.Sleep(1000);
  17. });
  18. t.ContinueWith(task =>
  19. {
  20. //做一些工作
  21. });
  22. }
  23. //有返回值转换前
  24. public async void Example()
  25. {
  26. Task<int> t = Task.Run<int>(() =>
  27. {
  28. Thread.Sleep(1000);
  29. return 1;
  30. });
  31. int res = await t;
  32. //使用res做一些工作
  33. }
  34. //有返回值转换后
  35. public void Example()
  36. {
  37. Task<int> t = Task.Run<int>(() =>
  38. {
  39. Thread.Sleep(1000);
  40. return 1;
  41. });
  42. t.ContinueWith(task =>
  43. {
  44. //使用task.Result做一些工作
  45. });
  46. }

  看起来不错,但至少有以下问题,如下:

  • 该种转换方法不能很好的转换Try/Catch结构
  • 在循环结构中使用await不好转换
  • 该实现与Task类型紧密联系

  一二点是我自己认为的,但第三点是可以从扩展async/await这点被证明的。但无论怎样,async/await只是对方法按照一定的规则进行了变换而已,它并没有什么特别之处,具体来讲,就是把Await后面要执行的代码放到一个类似ContinueWith的函数中,在C#中,它是以状态机的形式表现的,每个状态都对应一部分代码,状态机有一个MoveNext()方法,MoveNext()根据不同的状态执行不同的代码,然后每个状态部分对应的代码都会设置下一个状态字段,然后把自身的MoveNext()方法放到类似ContinueWith()的函数中去执行,整个状态机由回调函数推动。我们尝试手动转换以下async/await方法。

  1. public static Task WorkAsync()
  2. {
  3. return Task.Run(() =>
  4. {
  5. Thread.Sleep(1000);
  6. Console.WriteLine("Done!");
  7. });
  8. }
  9. public static async void Test()
  10. {
  11. Console.WriteLine("步骤1");
  12. await WorkAsync();
  13. Console.WriteLine("步骤2");
  14. await WorkAsync();
  15. Console.WriteLine("步骤3");
  16. }

  手动写一个简单的状态机类

  1. public class TestAsyncStateMachine
  2. {
  3. public int _state = 0;
  4. public void Start() => MoveNext();
  5. public void MoveNext()
  6. {
  7. switch(_state)
  8. {
  9. case 0:
  10. {
  11. goto Step0;
  12. }
  13. case 1:
  14. {
  15. goto Step1;
  16. }
  17. default:
  18. {
  19. Console.WriteLine("步骤3");
  20. return;
  21. }
  22. }
  23. Step0:
  24. {
  25. Console.WriteLine("步骤1");
  26. _state = 1;
  27. WorkAsync().ContinueWith(t => this.MoveNext());
  28. return;
  29. }
  30. Step1:
  31. {
  32. _state = -1;
  33. Console.WriteLine("步骤2");
  34. WorkAsync().ContinueWith(t => this.MoveNext());
  35. return;
  36. }
  37. }
  38. }

  而Test()方法则变成了这样

  1. public static void Test()
  2. {
  3. new TestAsyncStateMachine().Start();
  4. }

  注意Test()方法返回的是void,这意味这调用方将不能await Test()。如果返回Task,这个状态机类是不能正确处理的,如果要正确处理,那么状态机在Start()启动后,必须返回一个Task,而这个Task在整个状态机流转完毕后要变成完成状态,以便调用方在该Task上调用的ContinueWith得以继续执行,而就Task这个类而言,它是没有提供这种方法来主动控制Task的状态的,这个与JS中的Promise不同,JS里面用Reslove函数来主动控制Promise的状态,并导致在该Promise上面的Then链式调用得以继续完成,而在C#里面怎么做呢?既然使用了状态机来实现async/await,那么在转换一个返回Task的函数时肯定会遇到,怎么处理?后面讲。

  首先解决一下与Task类型紧密联系这个问题。

  从状态机中可以看到,主要使用到了Task中的ContinueWith这个函数,它的语义是在任务完成后,执行回调函数,通过回调函数拿到结果,这个编程风格也叫做CPS(Continuation-Passing-Style, 续体传递风格),那么我们能不能把这个函数给抽象出来呢?语言开发者当然想到了,它被抽象成了一个Awaiter因此编译器要求await的类型必须要有GetAwaiter方法,什么样的类型才是Awaiter呢?编译器规定主要实现了如下几个方法的类型就是Awaiter:

  • 必须继承INotifyCompletion接口,并实现其中的OnCompleted(Action continuation)方法
  • 必须包含IsCompleted属性
  • 必须包含GetResult()方法

  第一点好理解,第二点的作用是热路径优化,第三点以后讲。我们再改造一下我们手动写的状态机。

  1. public class TestAsyncStateMachine
  2. {
  3. public int _state = 0;
  4. public void Start() => MoveNext();
  5. public void MoveNext()
  6. {
  7. switch(_state)
  8. {
  9. case 0:
  10. {
  11. goto Step0;
  12. }
  13. case 1:
  14. {
  15. goto Step1;
  16. }
  17. default:
  18. {
  19. Console.WriteLine("步骤3");
  20. return;
  21. }
  22. }
  23. Step0:
  24. {
  25. Console.WriteLine("步骤1");
  26. _state = 1;
  27. TaskAwaiter taskAwaiter;
  28. taskAwaiter = WorkAsync().GetAwaiter();
  29. if (taskAwaiter.IsCompleted) goto Step1;
  30. taskAwaiter.OnCompleted(() => this.MoveNext());
  31. return;
  32. }
  33. Step1:
  34. {
  35. _state = -1;
  36. Console.WriteLine("步骤2");
  37. TaskAwaiter taskAwaiter;
  38. taskAwaiter = WorkAsync().GetAwaiter();
  39. if (taskAwaiter.IsCompleted) MoveNext();
  40. taskAwaiter.OnCompleted(() => this.MoveNext());
  41. return;
  42. }
  43. }
  44. }

  可以看到去掉了与Task中ContinueWith的耦合关系,并且如果任务已经完成,则可以直接执行下个任务,避免了无用的开销。

  因此我们可以总结一下async/await:

  • async/await只是表示这个方法需要编译器进行特殊处理,并不代表它本身一定是异步的。
  • Task类中的GetAwaiter主要是给编译器用的。

  第一点我们可以用以下例子来证明,有兴趣的朋友可以自己去验证以下,以便加深理解。

  1. //该类型包含GetAwaiter方法,且GetAwaiter()返回的类型包含三个必要条件
  2. public class MyAwaiter : INotifyCompletion
  3. {
  4. public void OnCompleted(Action continuation)
  5. {
  6. continuation();
  7. }
  8. public bool IsCompleted { get; }
  9. public void GetResult()
  10. {
  11. }
  12. public MyAwaiter GetAwaiter() => new MyAwaiter();
  13. }

  一个测试函数,注意必须返回void

  1. public static async void AwaiterTest()
  2. {
  3. await new MyAwaiter();
  4. Console.WriteLine("Done");
  5. }

  可以看到这是完全同步进行的。

  觉得有收获的不妨点个赞,有支持才有动力写出更好的文章。

C#异步编程由浅入深(二)Async/Await的作用.的更多相关文章

  1. 走进异步编程的世界--async/await项目使用实战

    起因:今天要做一个定时器任务:五分钟查询一次数据库发现超时未支付的订单数据将其状态改为已经关闭(数据量大约100条的情况) 开始未使用异步: public void SelfCloseGpPayOrd ...

  2. 异步编程新方式async/await

    一.前言 实际上对async/await并不是很陌生,早在阮大大的ES6教程里面就接触到了,但是一直处于理解并不熟练使用的状态,于是决定重新学习并且总结一下,写了这篇博文.如果文中有错误的地方还请各位 ...

  3. 【C# TAP 异步编程】二 、await运算符已经可等待类型Awaitable

    await的作用: 1.await是一个标记,告诉编译器生成一个等待器来等待可等待类型实例的运行结果. 2.一个await对应一个等待器 ,任务的等待器类型是TaskAwaiter/TaskAwait ...

  4. ES7前端异步玩法:async/await理解 js原生API妙用(一)

    ES7前端异步玩法:async/await理解   在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是“异步”的意思,a ...

  5. Dart 异步编程(二):async/await

    对每一个异步任务返回的 Future 对象都使用链式操作-- then ,超过三个以上时,导致"倒三角"现象,降低代码的可阅读性. getHobbies() { post('htt ...

  6. C#基础系列——异步编程初探:async和await

    前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法.确实,没有异步的多线程是单调的.乏味的,async和await是出现在C#5.0之后,它的出现给了 ...

  7. 【转】剖析异步编程语法糖: async和await

    一.难以被接受的async 自从C#5.0,语法糖大家庭又加入了两位新成员: async和await. 然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模 ...

  8. [C#]剖析异步编程语法糖: async和await

    一.难以被接受的async 自从C#5.0,语法糖大家庭又加入了两位新成员: async和await. 然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模 ...

  9. 【异步编程】Part1:await&async语法糖让异步编程如鱼得水

    前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...

随机推荐

  1. NGK 路演美国站,SPC空投与NGK项目安全

    最近,NGK全球巡回路演在美国最大城市纽约市落下帷幕,本次路演有幸邀请了NGK方面代表迈尔逊,纽约当地区块链大咖维克多以及美国当地社群意见代表乔治等人. 路演一开始,美国当地路演师Viko首先致辞,V ...

  2. mysql数据库表引入redis解决方案

    缓存方案 缓存方案在我的另外一篇博客里有详细说明,地址:https://www.cnblogs.com/wingfirefly/p/14419728.html 数据结构: 方案1: 1.存储结构采用h ...

  3. 远程过程调用框架——gRPC

    gRPC是一款基于http协议的远程过程调用(RPC)框架.出自google.这个框架可以用来相对简单的完成如跨进程service这样的需求开发. 资料参考: https://blog.csdn.ne ...

  4. 最小生成树---普里姆算法(Prim算法)和克鲁斯卡尔算法(Kruskal算法)

    普里姆算法(Prim算法) #include<bits/stdc++.h> using namespace std; #define MAXVEX 100 #define INF 6553 ...

  5. java安全管理器SecurityManager

    本文转载自java安全管理器SecurityManager 导语 这是一篇对Java安全管理器入门的文章,目的是简单了解什么是SecurityManager,对管理器进行简单配置,解决简单问题. 比如 ...

  6. 1086 Tree Traversals Again——PAT甲级真题

    1086 Tree Traversals Again An inorder binary tree traversal can be implemented in a non-recursive wa ...

  7. dategrip的使用技巧

    原文链接:https://blog.csdn.net/weixin_44421461/article/details/109541903 数据表复制,可以直接用sql语句 1.复制表结构及数据到新表 ...

  8. Java并发之ThreadPoolExecutor源码解析(二)

    ThreadPoolExecutor ThreadPoolExecutor是ExecutorService的一种实现,可以用若干已经池化的线程执行被提交的任务.使用线程池可以帮助我们限定和整合程序资源 ...

  9. python进阶(11)生成器

    生成器 利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成.但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据. ...

  10. Linux下搭建RocketMQ环境

    Apache 官网: http://rocketmq.apache.org/ RocketMQ 的 Github 地址: English:https://github.com/apache/rocke ...