当前位置: 首页 > news >正文

.NET 除了用 Task 之外,如何自己写一个可以 await 的对象?

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

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


本文内容

      • Awaiter 系列文章
      • 可等待对象
      • 编写基本的 Awaiter 框架代码
      • 实现基本的 Awaiter
      • 实现异步的 Awaiter

Awaiter 系列文章

入门篇:

  • .NET 中什么样的类是可使用 await 异步等待的?
  • 定义一组抽象的 Awaiter 的实现接口,你下次写自己的 await 可等待对象时将更加方便
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?

实战篇:

  • 在 WPF/UWP 中实现一个可以用 await 异步等待 UI 交互操作的 Awaiter
  • .NET 编写一个可以异步等待循环中任何一个部分的 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 中什么样的类是可使用 await 异步等待的?
  • Visual Studio 2017 以前的旧格式的 csproj Import 进来的 targets 文件有时不能正确计算属性(PropertyGroup)和集合(ItemGroup)
  • 使用 ReSharper,输入即遵循 StyleCop 的代码格式化规范
  • StyleCop 是什么,可以帮助团队带来什么价值?
  • 文件和文件夹不存在的时候,FileSystemWatcher 监听不到文件的改变?如果递归地监听就可以了
  • C#/.NET 使用 CommandLineParser 来标准化地解析命令行
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • 使用 WPF 开发一个 Windows 屏幕保护程序
  • 在 Windows 10 中开启移动 WLAN 热点
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • 在 Roslyn 分析语法树时添加条件编译符号的支持
  • 自然码的形码
  • 出于迁移项目的考虑,GitHub 中 Fork 出来的项目,如何与原项目断开 Fork 关系?
  • 只需 5 秒钟,你就能取到 WPF 程序的超高分辨率超高清截图
  • 谨慎使用 FileInfo.Exists 实例方法,而是使用 File.Exists 静态方法替代
  • [PHP内核探索]PHP中的哈希表
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • Angular数据绑定机制
  • express.js的介绍及使用
  • leetcode98. Validate Binary Search Tree
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • PAT A1017 优先队列
  • Promise面试题2实现异步串行执行
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • Vue2 SSR 的优化之旅
  • yii2中session跨域名的问题
  • 简单实现一个textarea自适应高度
  • 简单易用的leetcode开发测试工具(npm)
  • 嵌入式文件系统
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 微信开放平台全网发布【失败】的几点排查方法
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 你对linux中grep命令知道多少?
  • Linux权限管理(week1_day5)--技术流ken
  • postgresql行列转换函数
  • 从如何停掉 Promise 链说起
  • # 达梦数据库知识点
  • (13)Hive调优——动态分区导致的小文件问题
  • (2)STL算法之元素计数
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (vue)el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)
  • (阿里云万网)-域名注册购买实名流程
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (分类)KNN算法- 参数调优
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (转载)OpenStack Hacker养成指南
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • .NET设计模式(2):单件模式(Singleton Pattern)
  • .net网站发布-允许更新此预编译站点
  • .pub是什么文件_Rust 模块和文件 - 「译」