.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, ...
随机推荐
- android开发:Android 中自定义属性(attr.xml,TypedArray)的使用
今天我们的教程是根据前面一节扩展进行的,如果你没有看,请点击 Android高手进阶教程(三)查看第三课,这样跟容易方便你的理解! 在xml 文件里定义控件的属性,我们已经习惯了android:att ...
- SQL Timeout超时的处理方法
第一步:修改Web.config配置文件.在数据库连接字符串中加上连接时间Connect Timeout,根据实际情况定时间. <!--连接数据库--> <connectionStr ...
- 1月24日 ruby基础3部分 Numeric, Array已学。
<div style="background:lightblue"> 第12章 数值类 12.1 数值的构成 Numeric-> Integer-> Fix ...
- android--------volley之网络请求和图片加载
Volley是 Google 推出的 Android 异步网络请求框架和图片加载框架. Volley的特性 封装了的异步的请求API.Volley 中大多是基于接口的设计,可配置性强. 一个优雅和稳健 ...
- Nastya and King-Shamans CodeForces - 992E (线段树二分)
大意: 给定序列a, 单点更新, 询问是否存在a[i]等于s[i-1], s为a的前缀和, a非负 考虑到前缀和的单调性, 枚举从1开始前缀和, 找到第一个大于等于s[1]的a[i], 如果相等直接输 ...
- php表单提交安全方法
1.$_SERVER["PHP_SELF"] 将表单数据发送到页面本身,而不是跳转到另一张页面.这样,用户就能够在表单页面获得错误提示信息.2.通过使用 htmlspecialch ...
- Leetcode 82
有个错误就是member access within null pointer of type 'struct ListNode' 其实就是判断了指针是否异常了,比如NULL->next之类.要 ...
- Xshell如何设置,当连接断开时保留Session,保留原文字
Xshell [1] 是一个强大的安全终端模拟软件,它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议.Xshell 通过互联网到远程主机的安全连接以及它 ...
- turbine是怎么收集指标数据的
turbine是怎么收集指标数据的 我们通过spring cloud图形化dashboard是如何实现指标的收集展示的知道了,图形化的指标是从turbine获取到指标数据的.那么turbine的数据是 ...
- Oracle12c中性能优化&功能增强新特性之重大突破——内存列存储新特性
内存列存储(IM column store) 是Oracle12.1.0.2版本的主要特点.该特点允许列,表,分区和物化视图在内存中以列格式存储,而不是通常的行格式.数据存在内存中的好处显而易见,而列 ...