async和await作为异步模型代码编写的语法糖已经提供了一段时间不过一直没怎么用,由于最近需要在BeetleX webapi中集成对Task方法的支持,所以对async和await有了深入的了解和实践应用.在这总结一下async和await的使用,主要涉及到:自定义Awaitable,在传统异步方法中集成Task,异常处理等.

介绍

在传统异步方法处理都是通过指定回调函数的方式来进行处理,这样对于业务整非常不方便.毕竟业务信息和状态往往涉及到多个异步回调,这样业务实现和调试成本都非常高.为了解决这一问题dotnet推出了async和await语法糖,该语法可以把编写的代码编译成状态机模式,从而让开发员以同步的代码方式实现异步功能的应用.

应用

async和await的使用非常简单,只需要在方法前加上async关键字,然后await所有返回值为Task或ValueTask的方法即可.大概应用如下:

  1. async void AccessTheWebAsync()
  2. {
  3. var client = new HttpClient();
  4. var result = await client.GetStringAsync("https://msdn.microsoft.com");
  5. Console.WriteLine(result);
  6. }

以上是HttpClient的一个简单应用,它和传统的同步调用有什么不同呢?如果用同步GetString那线程回等待网络请求完成后再进行输出,这样会导致线程资源一直浪费在那里.使用await后,当线程执行GetStringAsync后就会释放出来,然后由网络回调线程来触发后面的代码执行.当然还有一种情况就是GetStringAsync同步完成了当线程就会马上执行Console.WriteLine(result);其实不管那一种情况下都不会让线程等待在那里浪费资源.

自定义Awaitable

一般情况下async和await都是结合Task来使用,因此可能有人感觉async和await是因Task而存在的;其实async和await是一个语法糖,通过它和相应的代码规则来让编译器知道怎样做,但这个规则并不是Task;正确的来说Task是这规则的一种实现,然后应用在大量的方法上,所以自然就使用起来就最普遍了.如果感觉Task太繁琐使用起来比较重的情况下是完全可以自己实现这个规则,这一规则实现起来也很简单只需要简单地实现一个接口和定义一些方法即可:

  1. public interface INotifyCompletion
  2. {
  3. void OnCompleted(Action continuation);
  4. }

看上去是不是很简单,不过除了实现这一接口外,还需要定义一些固定名称的方法

  1. public interface IAwaitCompletion : INotifyCompletion
  2. {
  3.  
  4. bool IsCompleted { get; }
  5.  
  6. void Success(object data);
  7.  
  8. void Error(Exception error);
  9.  
  10. }
  11.  
  12. public interface IAwaitObject : IAwaitCompletion
  13. {
  14.  
  15. IAwaitObject GetAwaiter();
  16.  
  17. object GetResult();
  18.  
  19. }

在基础上再定义一下些行为就可以了,以上IAwaitObject就是实现一个Awaitable所需要的基础方法行为.不过Success和'Error'方法不是必需要.只是通过这些方法可以让外部来触发OnCompleted行为而已. 围绕接口实现Awaitable的方式也可以根据实际情况应用有所不同,只要需要确保基础规则实现即可,以下是针对SocketAsyncEventArgs实现的Awaitable

  1. public class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyCompletion
  2. {
  3. private static readonly Action _callbackCompleted = () => { };
  4.  
  5. private readonly PipeScheduler _ioScheduler;
  6.  
  7. private Action _callback;
  8.  
  9. public SocketAwaitableEventArgs(PipeScheduler ioScheduler)
  10. {
  11. _ioScheduler = ioScheduler;
  12. }
  13.  
  14. public SocketAwaitableEventArgs GetAwaiter() => this;
  15.  
  16. public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
  17.  
  18. public int GetResult()
  19. {
  20. Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
  21.  
  22. _callback = null;
  23.  
  24. if (SocketError != SocketError.Success)
  25. {
  26. ThrowSocketException(SocketError);
  27. }
  28.  
  29. return BytesTransferred;
  30.  
  31. void ThrowSocketException(SocketError e)
  32. {
  33. throw new SocketException((int)e);
  34. }
  35. }
  36.  
  37. public void OnCompleted(Action continuation)
  38. {
  39. if (ReferenceEquals(_callback, _callbackCompleted) ||
  40. ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted))
  41. {
  42. Task.Run(continuation);
  43. }
  44. }
  45.  
  46. public void UnsafeOnCompleted(Action continuation)
  47. {
  48. OnCompleted(continuation);
  49. }
  50.  
  51. public void Complete()
  52. {
  53. OnCompleted(this);
  54. }
  55.  
  56. protected override void OnCompleted(SocketAsyncEventArgs _)
  57. {
  58. var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
  59.  
  60. if (continuation != null)
  61. {
  62. _ioScheduler.Schedule(state => ((Action)state)(), continuation);
  63. }
  64. }
  65. }

以上是Kestrel内部实现的一个Awaitable,它的好处就是可以自己不停地复用,并不需要每次await都要构建一个Task对象.这样对于大量处理的情况下可以降低对象的开销减轻GC的负担来提高性能.

传统异步下实现async/await

其实自定义Awaitable就是一种传统异步使用async/await功能的一种实现,但对于普通开发人员来说对于状态不好控制的情况那实现这个Awaitable多多少少有些困难,毕竟还需要大量的测试工作来验证.其实dotnet已经提供TaskCompletionSource<T>对象来方便应用开发者在传统异步下简单实现async/await.这个对象使用起来也非常方便

  1. public Task<Response> Execute()
  2. {
  3. TaskCompletionSource<Response> taskCompletionSource = new TaskCompletionSource<Response>();
  4. OnExecute(taskCompletionSource);
  5. return taskCompletionSource.Task;
  6. }

构建一个TaskCompletionSource<T>对象返回对应的Task即可,然后在异步完成的地方调用相关方法即可简单实现传统异步支持async/await

  1. taskCompletionSource.TrySetResult(response)

  1. taskCompletionSource.TrySetError(exception)

在这里不得不说一下TaskCompletionSource<T>的设计,非要加个泛型.如果结合反射使用就有点蛋碎了,毕竟这个方法并不提供object设置,除非上层定义TaskCompletionSource<Object>但这样定义就失去了T的意义了....还好这个类可继承的给使用者留了一个后路.以下做了简单的封装让它支持object返回值传入

  1. interface IAnyCompletionSource
  2. {
  3. void Success(object data);
  4. void Error(Exception error);
  5. void WaitResponse(Task<Response> task);
  6. Task GetTask();
  7. }
  8.  
  9. class AnyCompletionSource<T> : TaskCompletionSource<T>, IAnyCompletionSource
  10. {
  11. public void Success(object data)
  12. {
  13. TrySetResult((T)data);
  14. }
  15.  
  16. public void Error(Exception error)
  17. {
  18. TrySetException(error);
  19. }
  20.  
  21. public async void WaitResponse(Task<Response> task)
  22. {
  23. var response = await task;
  24. if (response.Exception != null)
  25. Error(response.Exception);
  26. else
  27. Success(response.Body);
  28. }
  29.  
  30. public Task GetTask()
  31. {
  32. return this.Task;
  33. }
  34. }

异常处理

由于async/await最终编译成状态机代码,所以异常处理会和普通代码不同,一连串的async/await方法里,一般只需要在最顶的断层方法Try即可,一般这个断层的方法是async void,或Task.wait处;和传统方法异常处理不一样,如果再往上一层是无法Try住这些异常的,当现现这情况的时候往往就是未知异常导致程序死掉.以下是一个错误的处理代码:

  1. static void Main(string[] args)
  2. {
  3. try
  4. {
  5. Test();
  6. }
  7. catch (Exception e_)
  8. {
  9. Console.WriteLine(e_);
  10. }
  11. Console.Read();
  12. }
  13.  
  14. static async void Test()
  15. {
  16. Console.WriteLine(await PrintValue());
  17. }
  18.  
  19. static async Task<bool> PrintValue()
  20. {
  21. var value = await GetUrl();
  22. Console.WriteLine(value);
  23. return true;
  24. }
  25.  
  26. static async Task<string> GetUrl()
  27. {
  28. var client = new HttpClient();
  29. return await client.GetStringAsync("https://msdn.microsoft.comasd");
  30. }

正确有效的Try地方是在Test方法里

  1. static async void Test()
  2. {
  3. try
  4. {
  5. Console.WriteLine(await PrintValue());
  6. }
  7. catch (Exception e_)
  8. {
  9. Console.WriteLine(e_);
  10. }
  11. }
  12.  
  13. static async Task<bool> PrintValue()
  14. {
  15. var value = await GetUrl();
  16. Console.WriteLine(value);
  17. return true;
  18. }
  19.  
  20. static async Task<string> GetUrl()
  21. {
  22. var client = new HttpClient();
  23. return await client.GetStringAsync("https://msdn.microsoft.comasd");
  24. }

一些注意事项和技巧

  1. 自定义async/await时候,默认都是由异步完成线程来触发状态机,但这里存在一个风险当这个触发状态机的代码是在锁范围内执行就需要特别小心,很多时候再次回归执行获取锁的时候就导致无法得到引起代码无法执行的问题.
  2. 在使用的await之前其实是可以先判断一下完成状态,如果是完成就没有必然引用await来处理状态机的工作,这样一定程度降低状态的执行和开销.
  3. 如果你的方法可以是同步完成,如一些内存操作那最好用ValueTask代替Task
  4. 其实反射里使用async/await也是非常方便的,只需要判断一下对象是否Awaitable,如果是就执行await处理状态机.

async/await使用深入详解的更多相关文章

  1. async/await 执行顺序详解

    随着async/await正式纳入ES7标准,越来越多的人开始研究据说是异步编程终级解决方案的 async/await.但是很多人对这个方法中内部怎么执行的还不是很了解,本文是我看了一遍技术博客理解 ...

  2. C# 中的Async 和 Await 的用法详解

    众所周知C#提供Async和Await关键字来实现异步编程.在本文中,我们将共同探讨并介绍什么是Async 和 Await,以及如何在C#中使用Async 和 Await. 同样本文的内容也大多是翻译 ...

  3. 关于async和await的一些误区实例详解

    转载自 http://www.jb51.net/article/53399.htm 这篇文章主要介绍了关于async和await的一些误区实例详解,有助于更加深入的理解C#程序设计,需要的朋友可以参考 ...

  4. 详解promise、async和await的执行顺序

    1.题目和答案 一道题题目:下面这段promise.async和await代码,请问控制台打印的顺序? async function async1(){ console.log('async1 sta ...

  5. 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程

    反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑)   背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...

  6. Promise和async await详解

    本文转载自Promise和async await详解 Promise 状态 pending: 初始状态, 非 fulfilled 或 rejected. fulfilled: 成功的操作. rejec ...

  7. JavaScript中的async/await详解

    1.前言 ​ async函数,也就是我们常说的async/await,是在ES2017(ES8)引入的新特性,主要目的是为了简化使用基于Promise的API时所需的语法.async和await关键字 ...

  8. async和await详解

     async和await详解 1.非UI线程中执行 Test()函数带有async 和await ,返回值写成Task. 1 using System; 2 using System.Threadin ...

  9. async await详解

    async await本身就是promise + generator的语法糖. 本文主要讲述以下内容 async awiat 实质 async await 主要特性 async await 实质 下面 ...

随机推荐

  1. Maven常识

    maven下面通常有四个文件夹: src/main/java -- 用来存放业务代码 src/test/java -- 用来存放测试代码 另有两个名为resource的文件夹,通常用来放置前两个文件夹 ...

  2. 使用代码的方式给EntityFramework edmx 创建连接字符串

    在构建上下文的时候动态生成连接字符串: /// <summary> /// 从配置生成连接 /// </summary> private static readonly str ...

  3. java.lang.IllegalArgumentException异常 返回值类型的问题

    java.lang.IllegalArgumentException: Cannot create TypedQuery for query with more than one return usi ...

  4. 学习React Native必看的几个开源项目

    学习React native ,分享几个不错的开源项目,相信你学完之后,一定会有所收获.如果还没有了解RN的同学们可以参考手把手教你React Native 实战之开山篇<一> 1.Fac ...

  5. Ubuntu18.04 Desktop Entry

    1.Desktop Entry 是什么? 我们都知道,在Windows里软件在安装的时候都会询问是不是要在开始菜单和桌面创建快捷方式,这样就不用在使用软件的时候去安装目录启动,而是直接去开始菜单点击相 ...

  6. 如何扩展分布式日志组件(Exceptionless)的Webhook事件通知类型?

    写在前面 从上一篇博客高并发.低延迟之C#玩转CPU高速缓存(附示例)到现在又有几个月没写博客了,啥也不说,变得越来越懒了,懒惰产生了拖延后遗症. 最近一周升级了微服务项目使用的分布式日志组件Exce ...

  7. 带着新人看java虚拟机04(多线程篇)

    我记得最开始接触多进程,多线程这一块的时候我不是怎么理解,为什么要有多线程啊?多线程到底是个什么鬼啊?我一个程序好好的就可以运行为什么要用到多线程啊?反正我是十分费解,即使过了很长时间我还是不是很懂, ...

  8. Java进阶篇设计模式之九----- 解释器模式和迭代器模式

    前言 在上一篇中我们学习了行为型模式的责任链模式(Chain of Responsibility Pattern)和命令模式(Command Pattern).本篇则来学习下行为型模式的两个模式, 解 ...

  9. 【极简版】SpringBoot+SpringData JPA 管理系统

    前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 在上一篇中已经讲解了如何从零搭建一个SpringBo ...

  10. Eclipse4JavaEE安装Gradle,并导入我们的Gradle项目

    第一步:下载Gradle Gradle下载链接,如下图,下载最新版本即可.下载下来的zip包,解压到一个目录即可,如F盘 第二步:配置环境变量 首先添加GRADLE_HOME,如下图 然后在Path下 ...