zoukankan      html  css  js  c++  java
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?

    .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();
    }
    

    程序运行结果

  • 相关阅读:
    SQL Server中六种数据移动的方法
    SQL Server数据导入导出技术概述与比较
    深入浅出SQL之左连接、右连接和全连接
    安装SQL server提示安装不上,挂起之类
    如何在SQL Server中快速删除重复记录
    SQL Server数据导入导出工具BCP详解
    深入浅出SQL教程之嵌套SELECT语句
    使用osql执行sql脚本
    sql server 分组统计
    php常用几种设计模式的应用场景
  • 原文地址:https://www.cnblogs.com/walterlv/p/10236384.html
Copyright © 2011-2022 走看看