.NET 中的 async / await 写异步代码用起来真的很爽,就像写同步一样。我们可以在各种各样的异步代码中看到 Task 返回值,这样大家便可以使用 await 等待这个方法。不过,有时需要写一些特别的异步方法,这时需要自己来实现一个可以异步等待的对象。

本文将讲述如何实现一个可等待对象,一个自定义的 Awaiter。


Awaiter 系列文章

入门篇:

实战篇:

可等待对象

我们希望大家在调用下面的 CallWalterlvAsync 方法的时候,可以使用 await 关键字来异步等待:

  1. await CallWalterlvAsync();
  1. public WalterlvOperation CallWalterlvAsync()
  2. {
  3. // 返回一个 WalterlvOperation,以便外面调用方可以异步等待。
  4. return new WalterlvOperation();
  5. }

所以我们需要实现一个 WalterlvOperation

编写基本的 Awaiter 框架代码

先写一个空的类型,然后为它编写一个空的 GetAwaiter 方法,返回新的 WalterlvAwaiter 类型。

  1. /// <summary>
  2. /// 委托 walterlv 来完成一项特殊的任务。
  3. /// 通过在代码当中调用,可以让他在现实中为你做一些事情。
  4. /// </summary>
  5. public class WalterlvOperation
  6. {
  7. public WalterlvAwaiter GetAwaiter()
  8. {
  9. return new WalterlvAwaiter();
  10. }
  11. }

接着,我们编写 WalterlvAwaiter 类:

  1. public class WalterlvAwaiter : INotifyCompletion
  2. {
  3. public bool IsCompleted { get; }
  4. public void GetResult() { }
  5. public void OnCompleted(Action continuation) { }
  6. }

必须实现 INotifyCompletion 接口,此接口带来了 OnCompleted 方法。另外两个方法不是接口带来的,但是也是实现一个自定义的 Awaiter 必要的方法。

在你编写完以上两段代码之后,你的 await 就可以编译通过了。

额外说明一下,GetResult 方法是可以修改返回值的,只要返回值不是 void,那么 await 等待的地方将可以在 await 完成之后获得一个返回值。

  1. public class WalterlvAwaiter : INotifyCompletion
  2. {
  3. public bool IsCompleted { get; }
  4. public string GetResult() { }
  5. public void OnCompleted(Action continuation) { }
  6. }
  1. // 于是你可以拿到一个字符串类型的返回值。
  2. string result = await CallWalterlvAsync("写博客");

实现基本的 Awaiter

以上代码只能编译通过,但实际上如果你跑起来,会发现 await 一旦进入,是不会再往下执行的。因为我们还没有实现 WalterlvAwaiter 类型。

最重要的,是需要调用 OnCompleted 方法传入的 continuation 委托。

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

像以上这么写之后,await 之后的代码便可以执行了。

如果你只是希望了解如何实现一个 Awaiter,那么写出以上的代码就足以。因为这才是最本质最核心的 Awaiter 的实现。

不过,以上代码的执行是立即执行,没有任何异步的效果。因为 OnCompleted 被调用的时候,我们立刻调用了 continuation 的执行。

实现异步的 Awaiter

要真正达到异步的效果,OnCompleted 执行的时候,我们不能立刻去调用参数传进来的委托,而只是将他记录下来,等到任务真正完成的时候再去调用。

以下的代码就不再是通用的代码了,你需要针对你的不同业务去设计如何异步完成一个任务,然后再通知到异步等待的代码继续执行。

例如,现在我们期望 walterlv 代理去写博客,于是我们为 WalterlvOperation 加一点功能,真正去做一些异步的事情。

CallWalterlvAsync 的实现现在真的开启了一个异步操作。

  1. public WalterlvOperation CallWalterlvAsync(string task)
  2. {
  3. var operation = new WalterlvOperation(task);
  4. operation.Start();
  5. return operation;
  6. }

然后为了实现我们自己添加的 Start 方法,我们在里面去做一些事情。里面第一句就离开了当前线程前往线程池中的其他线程去执行 Console.WriteLine 了。

  1. /// <summary>
  2. /// 委托 walterlv 来完成一项特殊的任务。
  3. /// 通过在代码当中调用,可以让他在现实中为你做一些事情。
  4. /// </summary>
  5. public class WalterlvOperation
  6. {
  7. private readonly string _task;
  8. private readonly WalterlvAwaiter _awaiter;
  9. public WalterlvOperation(string task)
  10. {
  11. _task = task;
  12. _awaiter = new WalterlvAwaiter();
  13. }
  14. public async void Start()
  15. {
  16. await Task.Delay(100).ConfigureAwait(false);
  17. Console.WriteLine($"walterlv 已经收到任务:{_task}");
  18. Console.WriteLine($"开始执行");
  19. await Task.Delay(2000).ConfigureAwait(false);
  20. Console.WriteLine($"walterlv 已经完成 {_task}。");
  21. _awaiter.ReportCompleted();
  22. }
  23. /// <summary>
  24. /// 返回一个可等待对象,以便能够使用 await 关键字进行异步等待。
  25. /// </summary>
  26. public WalterlvAwaiter GetAwaiter()
  27. {
  28. return _awaiter;
  29. }
  30. }

于是现在可以通过下面的代码来要求 walterlv 去写博客了。

  1. await CallWalterlvAsync("写博客");

然而实际上,我们上面还留了一个 _awaiter.ReportCompleted 方法没有实现。由于我们的操作全部是异步的了,这个方法的实现就是为了通知所有正在使用 await 等待的代码,异步任务完成了,可以继续往后面执行了。

  1. public class WalterlvAwaiter : INotifyCompletion
  2. {
  3. private Action _continuation;
  4. public bool IsCompleted { get; private set; }
  5. public void GetResult()
  6. {
  7. // 这个函数我们暂时还没有真正实现,因为需要进行同步等待比较复杂。
  8. // 我们将在本文后面附的其他博客中实现。
  9. }
  10. public void OnCompleted(Action continuation)
  11. {
  12. // 当这个 Awaiter 被 await 等待的时候,此代码会被调用。
  13. // 每有一处 await 执行到,这里就会执行一次,所以在任务完成之前我们需要 +=。
  14. if (IsCompleted)
  15. {
  16. continuation?.Invoke();
  17. }
  18. else
  19. {
  20. _continuation += continuation;
  21. }
  22. }
  23. public void ReportCompleted()
  24. {
  25. // 由 WalterlvOperation 来通知这个任务已经完成。
  26. IsCompleted = true;
  27. var continuation = _continuation;
  28. _continuation = null;
  29. continuation?.Invoke();
  30. }
  31. }

现在运行程序,会按照异步任务来执行,可以异步等待:

  1. static async Task Main(string[] args)
  2. {
  3. await CallWalterlvAsync("写博客");
  4. Console.Read();
  5. }

.NET 除了用 Task 之外,如何自己写一个可以 await 的对象?的更多相关文章

  1. 写一个为await自动加上catch的loader逐渐了解AST以及babel

    为什么要写这个loader 我们在日常开发中经常用到async await去请求接口,解决异步.可async await语法的缺点就是若await后的Promise抛出错误不能捕获,整段代码区就会卡住 ...

  2. 定义一组抽象的 Awaiter 的实现接口,你下次写自己的 await 可等待对象时将更加方便

    我在几篇文章中都说到了在 .NET 中自己实现 Awaiter 情况.async / await 写异步代码用起来真的很爽,就像写同步一样.然而实现 Awaiter 没有现成的接口,它需要你按照编译器 ...

  3. 手写一个简版 asp.net core

    手写一个简版 asp.net core Intro 之前看到过蒋金楠老师的一篇 200 行代码带你了解 asp.net core 框架,最近参考蒋老师和 Edison 的文章和代码,结合自己对 asp ...

  4. 如果你想深刻理解ASP.NET Core请求处理管道,可以试着写一个自定义的Server

    我们在上面对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了详细介绍(<聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer& ...

  5. 写一个ajax程序就是如此简单

    写一个ajax程序就是如此简单 ajax介绍: 1:AJAX全称为Asynchronous JavaScript and XML(异步JavaScript和XML),指一种创建交互式网页应用的网页开发 ...

  6. 操刀 requirejs,自己动手写一个

    前沿 写在文章的最前面 这篇文章讲的是,我怎么去写一个 requirejs . 去 github 上fork一下,顺便star~ requirejs,众所周知,是一个非常出名的js模块化工具,可以让你 ...

  7. 自己动手写一个iOS 网络请求库的三部曲[转]

    代码示例:https://github.com/johnlui/Swift-On-iOS/blob/master/BuildYourHTTPRequestLibrary 开源项目:Pitaya,适合大 ...

  8. 写一个Windows上的守护进程(8)获取进程路径

    写一个Windows上的守护进程(8)获取进程路径 要想守护某个进程,就先得知道这个进程在不在.我们假设要守护的进程只会存在一个实例(这也是绝大部分情形). 我是遍历系统上的所有进程,然后判断他们的路 ...

  9. C# Task中的Func, Action, Async与Await的使用

    在说Asnc和Await之前,先说明一下Func和Action委托, Task任务的基础的用法 1. Func Func是一种委托,这是在3.5里面新增的,2.0里面我们使用委托是用Delegate, ...

随机推荐

  1. Python - os 文件/目录操作

    最近经常用到 os 操作文件/目录,感觉挺好使的,但是一直没有系统的梳理学习一下, 今天想借此机会整理一下工作中常用的方法,也算自己总结学习的一个积累吧. 直接上代码,注释明了 #-*-coding: ...

  2. Analysis of single cell RNA-seq data(单细胞终极课程)

    业界良心啊,开源的单细胞课程. 随便看了几章,课程写得非常用心,非常适合新手. 课程地址:Analysis of single cell RNA-seq data 源码地址:hemberg-lab/s ...

  3. C# winform对话框用法大全

      对话框中我们常用了以下几种:1.文件对话框(FileDialog) 它又常用到两个: 打开文件对话框(OpenFileDialog) 保存文件对话(SaveFileDialog)2.字体对话框(F ...

  4. 库: rspec/rspec-expectations matcher匹配器常用

    https://github.com/rspec/rspec-expectations https://relishapp.com/rspec/rspec-expectations/v/3-7/doc ...

  5. 2-18,19 搭建MySQL主从服务器并并通过mysql-proxy实现读写分离

    MySQL主从服务器 实现方式: MySQL  REPLICATION Replication可以实现将数据从一台数据库服务器(master)复制到一台或多台数据库服务器(slave) 默认情况下这种 ...

  6. shell 命令参数

    $# 是传给脚本的参数个数$0 是脚本本身的名字$1 是传递给该shell脚本的第一个参数$2 是传递给该shell脚本的第二个参数$@ 是传给脚本的所有参数的列表$* 是以一个单字符串显示所有向脚本 ...

  7. LSTM CNN GRU DGA比较

    测试环境:linux,8cpu核,8G内存 优化后的模型比较 模型                         速度/eps          准确率 NN                    ...

  8. python中的注释,输入输出和编码及文件

    1.单行注释 以井号( # )开头,右边的所有内容当做说明2.多行注释 以三对单引号(’’’注释内容’’’)将注释包含起来以‘# ’是注释的标识符,可以记录当前代码所代表的意义,解释器会自动忽略这部分 ...

  9. CCF 高速公路 tarjan求强连通分量

    问题描述 某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路. 现在,大臣们帮国王拟了一个修高速公路的 ...

  10. POJ 2263 最短路Floyd_warshall算法

    灰常开心的用Floyd变形写出来了.额.对米来说还是牺牲了一定的脑细胞的.然而.我发现.大牛们还可以神奇的用Kruskal求最大生成树的最小权值来写.也可以用Dijkatra变形来写.T_T....5 ...