.NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
.NET 中的 async / await 写异步代码用起来真的很爽,就像写同步一样。我们可以在各种各样的异步代码中看到 Task 返回值,这样大家便可以使用 await 等待这个方法。不过,有时需要写一些特别的异步方法,这时需要自己来实现一个可以异步等待的对象。
本文将讲述如何实现一个可等待对象,一个自定义的 Awaiter。
Awaiter 系列文章
入门篇:
- .NET 中什么样的类是可使用 await 异步等待的?
- 定义一组抽象的 Awaiter 的实现接口,你下次写自己的 await 可等待对象时将更加方便
- .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
实战篇:
可等待对象
我们希望大家在调用下面的 CallWalterlvAsync 方法的时候,可以使用 await 关键字来异步等待:
await CallWalterlvAsync();
public WalterlvOperation CallWalterlvAsync()
{
// 返回一个 WalterlvOperation,以便外面调用方可以异步等待。
return new WalterlvOperation();
}
所以我们需要实现一个 WalterlvOperation。
编写基本的 Awaiter 框架代码
先写一个空的类型,然后为它编写一个空的 GetAwaiter 方法,返回新的 WalterlvAwaiter 类型。
/// <summary>
/// 委托 walterlv 来完成一项特殊的任务。
/// 通过在代码当中调用,可以让他在现实中为你做一些事情。
/// </summary>
public class WalterlvOperation
{
public WalterlvAwaiter GetAwaiter()
{
return new WalterlvAwaiter();
}
}
接着,我们编写 WalterlvAwaiter 类:
public class WalterlvAwaiter : INotifyCompletion
{
public bool IsCompleted { get; }
public void GetResult() { }
public void OnCompleted(Action continuation) { }
}
必须实现 INotifyCompletion 接口,此接口带来了 OnCompleted 方法。另外两个方法不是接口带来的,但是也是实现一个自定义的 Awaiter 必要的方法。
在你编写完以上两段代码之后,你的 await 就可以编译通过了。
额外说明一下,GetResult 方法是可以修改返回值的,只要返回值不是 void,那么 await 等待的地方将可以在 await 完成之后获得一个返回值。
public class WalterlvAwaiter : INotifyCompletion
{
public bool IsCompleted { get; }
public string GetResult() { }
public void OnCompleted(Action continuation) { }
}
// 于是你可以拿到一个字符串类型的返回值。
string result = await CallWalterlvAsync("写博客");
实现基本的 Awaiter
以上代码只能编译通过,但实际上如果你跑起来,会发现 await 一旦进入,是不会再往下执行的。因为我们还没有实现 WalterlvAwaiter 类型。
最重要的,是需要调用 OnCompleted 方法传入的 continuation 委托。
public void OnCompleted(Action continuation)
{
continuation.Invoke();
}
像以上这么写之后,await 之后的代码便可以执行了。
如果你只是希望了解如何实现一个 Awaiter,那么写出以上的代码就足以。因为这才是最本质最核心的 Awaiter 的实现。
不过,以上代码的执行是立即执行,没有任何异步的效果。因为 OnCompleted 被调用的时候,我们立刻调用了 continuation 的执行。
实现异步的 Awaiter
要真正达到异步的效果,OnCompleted 执行的时候,我们不能立刻去调用参数传进来的委托,而只是将他记录下来,等到任务真正完成的时候再去调用。
以下的代码就不再是通用的代码了,你需要针对你的不同业务去设计如何异步完成一个任务,然后再通知到异步等待的代码继续执行。
例如,现在我们期望 walterlv 代理去写博客,于是我们为 WalterlvOperation 加一点功能,真正去做一些异步的事情。
CallWalterlvAsync 的实现现在真的开启了一个异步操作。
public WalterlvOperation CallWalterlvAsync(string task)
{
var operation = new WalterlvOperation(task);
operation.Start();
return operation;
}
然后为了实现我们自己添加的 Start 方法,我们在里面去做一些事情。里面第一句就离开了当前线程前往线程池中的其他线程去执行 Console.WriteLine 了。
/// <summary>
/// 委托 walterlv 来完成一项特殊的任务。
/// 通过在代码当中调用,可以让他在现实中为你做一些事情。
/// </summary>
public class WalterlvOperation
{
private readonly string _task;
private readonly WalterlvAwaiter _awaiter;
public WalterlvOperation(string task)
{
_task = task;
_awaiter = new WalterlvAwaiter();
}
public async void Start()
{
await Task.Delay(100).ConfigureAwait(false);
Console.WriteLine($"walterlv 已经收到任务:{_task}");
Console.WriteLine($"开始执行");
await Task.Delay(2000).ConfigureAwait(false);
Console.WriteLine($"walterlv 已经完成 {_task}。");
_awaiter.ReportCompleted();
}
/// <summary>
/// 返回一个可等待对象,以便能够使用 await 关键字进行异步等待。
/// </summary>
public WalterlvAwaiter GetAwaiter()
{
return _awaiter;
}
}
于是现在可以通过下面的代码来要求 walterlv 去写博客了。
await CallWalterlvAsync("写博客");
然而实际上,我们上面还留了一个 _awaiter.ReportCompleted 方法没有实现。由于我们的操作全部是异步的了,这个方法的实现就是为了通知所有正在使用 await 等待的代码,异步任务完成了,可以继续往后面执行了。
public class WalterlvAwaiter : INotifyCompletion
{
private Action _continuation;
public bool IsCompleted { get; private set; }
public void GetResult()
{
// 这个函数我们暂时还没有真正实现,因为需要进行同步等待比较复杂。
// 我们将在本文后面附的其他博客中实现。
}
public void OnCompleted(Action continuation)
{
// 当这个 Awaiter 被 await 等待的时候,此代码会被调用。
// 每有一处 await 执行到,这里就会执行一次,所以在任务完成之前我们需要 +=。
if (IsCompleted)
{
continuation?.Invoke();
}
else
{
_continuation += continuation;
}
}
public void ReportCompleted()
{
// 由 WalterlvOperation 来通知这个任务已经完成。
IsCompleted = true;
var continuation = _continuation;
_continuation = null;
continuation?.Invoke();
}
}
现在运行程序,会按照异步任务来执行,可以异步等待:
static async Task Main(string[] args)
{
await CallWalterlvAsync("写博客");
Console.Read();
}

.NET 除了用 Task 之外,如何自己写一个可以 await 的对象?的更多相关文章
- 写一个为await自动加上catch的loader逐渐了解AST以及babel
为什么要写这个loader 我们在日常开发中经常用到async await去请求接口,解决异步.可async await语法的缺点就是若await后的Promise抛出错误不能捕获,整段代码区就会卡住 ...
- 定义一组抽象的 Awaiter 的实现接口,你下次写自己的 await 可等待对象时将更加方便
我在几篇文章中都说到了在 .NET 中自己实现 Awaiter 情况.async / await 写异步代码用起来真的很爽,就像写同步一样.然而实现 Awaiter 没有现成的接口,它需要你按照编译器 ...
- 手写一个简版 asp.net core
手写一个简版 asp.net core Intro 之前看到过蒋金楠老师的一篇 200 行代码带你了解 asp.net core 框架,最近参考蒋老师和 Edison 的文章和代码,结合自己对 asp ...
- 如果你想深刻理解ASP.NET Core请求处理管道,可以试着写一个自定义的Server
我们在上面对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了详细介绍(<聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer& ...
- 写一个ajax程序就是如此简单
写一个ajax程序就是如此简单 ajax介绍: 1:AJAX全称为Asynchronous JavaScript and XML(异步JavaScript和XML),指一种创建交互式网页应用的网页开发 ...
- 操刀 requirejs,自己动手写一个
前沿 写在文章的最前面 这篇文章讲的是,我怎么去写一个 requirejs . 去 github 上fork一下,顺便star~ requirejs,众所周知,是一个非常出名的js模块化工具,可以让你 ...
- 自己动手写一个iOS 网络请求库的三部曲[转]
代码示例:https://github.com/johnlui/Swift-On-iOS/blob/master/BuildYourHTTPRequestLibrary 开源项目:Pitaya,适合大 ...
- 写一个Windows上的守护进程(8)获取进程路径
写一个Windows上的守护进程(8)获取进程路径 要想守护某个进程,就先得知道这个进程在不在.我们假设要守护的进程只会存在一个实例(这也是绝大部分情形). 我是遍历系统上的所有进程,然后判断他们的路 ...
- C# Task中的Func, Action, Async与Await的使用
在说Asnc和Await之前,先说明一下Func和Action委托, Task任务的基础的用法 1. Func Func是一种委托,这是在3.5里面新增的,2.0里面我们使用委托是用Delegate, ...
随机推荐
- 【Golang】Debug :decoding dwarf section info at offset 0x0: too short
解决方法 通过下面的方式升级dlv 来解决这个问题: go get -u github.com/derekparker/delve/cmd/dlv 下面是我记录的定位问题的过程 问题描述 博主升级到了 ...
- Java下载https文件上传到阿里云oss服务器
Java下载https文件上传到阿里云oss服务器 今天做了一个从Https链接中下载音频并且上传到OSS服务器,记录一下希望大家也少走弯路. 一共两个类: 1 .实现自己的证书信任管理器类 /** ...
- 单细胞 RNA-seq 10X Genomics
单细胞流程跑了不少,但依旧看不懂结果,是该好好补补了. 有些人可能会误会,觉得单细胞的RNA-seq数据很好分析,跟分析常规的RNA-seq应该没什么区别.今天的这篇文章2015年3月发表在Natur ...
- java final修饰变量时的一种情况
有如下一种场景. 1.在文件PaymentConfig.java中存在如下变量public static final desc="描述" 2.类Test.java中使用了desc变 ...
- Java 主要特性
Java 有下面的一些主要特性. 面向对象 在 Java 中,所有的都是对象.正式因为 Java 基于对象模型,所以 Java 更加容易进行扩展. Java语言提供类.接口和继承等面向对象的特性,为了 ...
- 百度定位SDK
按照官网要求配置SHA1和包名生成ak秘钥 生成秘钥命令: keytool -list -v -keystore debug.keystore 密码:原始密码为android 添加libs文件夹并在g ...
- 总结: MySQL(基础,字段约束,索引,外键,存储过程,事务)操作语法
1. 显示数据库列表 show databases; # 查看当前所有数据库 show databases \G #以行的方式显示 2. 在命令行中,执行sql语句 mysql -e 'show ...
- Linux磁盘管理,vi编辑器以及包管理器
一.Linux磁盘管理 Linux磁盘管理常用的三个命令为df,du,fdisk df:列出文件系统的整体磁盘使用量,利用这个命令来获取磁盘被占用了多少空间,,目前还剩下多少空间用法:df [-ahi ...
- iOS UI-微博案例(通过代码自定义Cell)
一.Model BWWeiBo数据模型 #import <Foundation/Foundation.h> @interface BWWeiBo : NSObject @property ...
- html frameset的介绍
frameset 元素可以定义一个框架集.它被用来组织多个窗口(框架).每个框架存有独立的文档. 属性 ①border 设置框架的边框粗细. ②bordercolor 设置框架的边框颜色. ③fram ...