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

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


Awaiter 系列文章

入门篇:

实战篇:

可等待对象

我们希望大家在调用下面的 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 的对象?的更多相关文章

  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. JavaScript权威指南--多媒体和图形编程

    知识要点 21.1节介绍如何用传统的JavaScript技术实现诸如图片翻转(鼠标指针移动到一张静态图片上切换成另外一张图片)这样的视觉效果. 21.2节介绍HTML5的<audio>和& ...

  2. C# DataTable列名不区分大小写

    一直很纠结的就是DataTable的列名如何才能规范,从Oracle取出的DataTable都是大写,最后尝试了一下,原来C#的DataTable列名并不区分大小写,具体例子如下: DataTable ...

  3. English trip -- Review Unit2 At school 在学校

    What do you need,Loki? I need an eraser What does he need? He needs a dictionary Where's my pencil? ...

  4. Turbolinks

    Turbolinks Turbolinks® makes navigating your web application faster. 功能: 自动优化导航. 无需server端配合.全HTML网页 ...

  5. 12月22日 update_columns,完成第9节。

    Update_columns(attributes) //等同于update_column 直接更新database. 使用UPdate SQL 语法. ⚠️ :忽略了validations, Cal ...

  6. 将 HttpPostedFile 转换成 Image 或者 Bitmap

    代码如下: HttpFileCollection httpfiles = context.Request.Files; files = httpfiles[i]; Image im = Image.F ...

  7. HDU 4597 Play Game (记忆化搜索博弈DP)

    题意 给出2*n个数,分两列放置,每列n个,现在alice和bob两个人依次从任意一列的对头或队尾哪一个数,alice先拿,且两个人都想拿最多,问alice最后能拿到数字总和的最大值是多少. 思路 4 ...

  8. HDU 3279 二分图最大匹配

    DES: 就是说对每个人都给你一个区间.但一个人只匹配一个数.问你满足匹配的人的序号字典序最大时的最大匹配是什么. 前几天刚做的UVALive 6322...当然是不一样的...那个要求的最大匹配的个 ...

  9. 下拉选择框 Spinner的用法。

    代码如下: package com.lixu.xialakuang; import android.app.Activity; import android.content.Context; impo ...

  10. re 正则

      如果直接给出字符,就是精确匹配.对于特殊字符- ,在正则表达式中要用转义字符\转义.   \d        一个数字, \w       任意单个字符,空白符除外(例 字母.数字或下划线 . 英 ...