首页 > 编程语言 >C# 实现你自己的异步方法

C# 实现你自己的异步方法

时间:2023-03-02 22:56:18浏览次数:32  
标签:异步 Task Console C# await SliderAwaiter awaiter 方法 public

背景

最近在重构自己曾经的代码, 具体需求是在Unity等待如一个模型动画, 一段ui动画 如下:

Await的目标

await的目标是一个可等待对象, 而拥有GetAwaiter方法并且该方法拥有合适返回值的目标即可称为可等待对象(暂时你还不需要知道返回值需要符合什么规则,待会儿Studio会告诉你)

首先,我们通过一些简单的代码来模拟Unity的Slider, 并且实现一个方法来模拟ui动画, 然后在实例化它来看看效果

public class Slider
{
    public void ValueTo(int value)
    {
        Task.Run(async () => 
        {
            Console.Write("HP: ");
            for (int i = 0; i < value; i++)
            {
                await Task.Delay(350);
                Console.Write("■");
            }
            Console.Write("\n");
        });
    }
}

效果不错, 现在你希望可以await slider.ValueTo 方法, 而不是再通过使用回调函数传来传去来监听如动画是否结束

首先我们知道await的目标是一个拥有GetAwaiter方法的对象

简单点, 我们令Slider本身是可等待的, 也即我们需要在Slider上实现一个GetAwaiter方法, 已知且其返回值有一定的限制, 我们假设其返回值为SliderAwaiter

public class SliderAwaiter
{

}
public class Slider
{
    public SliderAwaiter GetAwaiter()
    {
        SliderAwaiter awaiter = new SliderAwaiter();
        return awaiter;
    }
    public Slider ValueToAsync(int value)
    {
        /*
         * Task是可等待的, 这里直接返回Task即可在这里达成我们的目标
         * 但本文的主要讨论的是实现 自己 的异步方法
         * 并且UI动画你可以以此法逃课, 但播放动画就不好逃了
         */
        Task.Run(async () => 
        {
            Console.Write("HP: ");
            for (int i = 0; i < value; i++)
            {
                await Task.Delay(350);
                Console.Write("■");
            }
            Console.Write("\n");
        });
        return this;
    }
}

当我们尝试在Main中直接await ValueToAsync方法时, 编译器报错(“Program.SliderAwaiter”未包含“IsCompleted”的定义), 我们进行修补。又报错(“Program.SliderAwaiter”不实现“INotifyCompletion”), 继续修, 最后一个报错(“Program.SliderAwaiter”未包含“GetResult”的定义), 补上

经过修补, 我们得到这样的SliderAwaiter

public class SliderAwaiter : INotifyCompletion
{
    public bool IsCompleted => false;

    public void OnCompleted(Action continuation)
    {

    }
    public void GetResult() { }
}

直接await ValueToAsync测试,发现Slider表现正常, 但await并没有触发

这里暂时先直接告诉你答案, 你需要手动触发OnCompleted传入的continuation来触发await后的代码

简单改造一下SliderAwaiter和Slider得到下面的代码

public class SliderAwaiter : INotifyCompletion
{
    public bool IsCompleted => false;
    public Action onFinish;
    public void OnCompleted(Action continuation)
    {
        if (IsCompleted)
        {
            continuation?.Invoke();
            onFinish?.Invoke();
        }
        else
        {
            onFinish += continuation;
        }
    }
    /// <summary>
    /// 手动触发 continuation
    /// </summary>
    public void ReportResult()
    {
        onFinish?.Invoke();
    }
    public void GetResult() { }
}
public class Slider
{
    SliderAwaiter awaiter;
    public SliderAwaiter GetAwaiter()
    {
        SliderAwaiter awaiter = new SliderAwaiter();
        this.awaiter = awaiter;
        return awaiter;
    }
    public Slider ValueToAsync(int value)
    {
        Task.Run(async () => 
        {
            Console.Write("HP: ");
            for (int i = 0; i < value; i++)
            {
                await Task.Delay(350);
                Console.Write("■");
            }
            Console.Write("\n");
            awaiter?.ReportResult();
        });
        return this;
    }
}

在Main函数中测试一下, 神奇的事情发生了, await后的代码成功且正确地执行了

你可能觉得有些奇妙, 你只是手动触发了OnCompleted传入的后续操作(类似Task.ContinueWith)就成功触发了结束等待, 你可能也会尝试在ReportResult中多次执行continuation, 但await的后续代码依旧只执行一次

OnCompleted中的continuation是什么, 它做了什么

了解他之前, 你可能需要先了解await是如何运行的, 首先你需要知道await的实现是基于状态机

简单点, 我们把测试代码改的简单点

static async Task Main(string[] args)
{
    Console.WriteLine("before await");
    await Task.Delay(500);
    Console.WriteLine("after  await");
    Console.Read();
}

然后通过dnSpy看看生成的中间语言是什么样的, 从下图和标注你应该可以清晰地看出程序的整体走向

通过简单测试代码的中间语言我们了解了await的大致走向, 那么回到最初的问题,continuation究竟做了什么

还原我们刚刚模拟Unity Slider的代码, 继续借助dnSpy, 二话不说找到ReportResult方法并且打上断点, 看看他做了什么

是的!一进来就发现一个惊喜, 第一步就跳转到一个叫做MoveNext的方法, 最终经过一番闪转腾挪我们又回到了的状态机里!

我们可以通过手动执行continuation来手动触发我们自己的awaiter!

至此, 你应该了解了如何实现自己的异步方法, 并且它不用依赖Task, TaskCompletionSource

 

最后附上完整的测试代码, 希望本片文章对你的异步之旅有所帮助

internal class Program
{
    public class SliderAwaiter : INotifyCompletion
    {
        public bool IsCompleted => false;
        public Action onFinish;
        public void OnCompleted(Action continuation)
        {
            if (IsCompleted)
            {
                continuation?.Invoke();
                onFinish?.Invoke();
            }
            else
            {
                onFinish += continuation;
            }
        }
        /// <summary>
        /// 手动触发 continuation
        /// </summary>
        public void ReportResult()
        {
            onFinish?.Invoke();
        }
        public void GetResult() { }
    }
    public class Slider
    {
        SliderAwaiter awaiter;
        public SliderAwaiter GetAwaiter()
        {
            SliderAwaiter awaiter = new SliderAwaiter();
            this.awaiter = awaiter;
            return awaiter;
        }
        public Slider ValueToAsync(int value)
        {
            Task.Run(async () => 
            {
                Console.Write("HP: ");
                for (int i = 0; i < value; i++)
                {
                    await Task.Delay(350);
                    Console.Write("■");
                }
                Console.Write("\n");
                awaiter?.ReportResult();
            });
            return this;
        }
    }

    static async Task Main(string[] args)
    {
        Slider slider = new Slider();
        Console.WriteLine("[ START ] PLAY UI_ANIMATION");
        await slider.ValueToAsync(4);
        Console.WriteLine("[  END  ] PLAY UI_ANIMATION");
    }

 

标签:异步,Task,Console,C#,await,SliderAwaiter,awaiter,方法,public
From: https://www.cnblogs.com/LaNashTT/p/17173471.html

相关文章

  • PVE版本升级及内核升级方法
    1.版本升级登录管理页面--点击左侧菜单栏的"PVE"节点--点击"更新"菜单--点击"刷新"按钮(实为执行apt-getupdate)---点击"升级"按钮---升级完重启pve2.更新内核更新一下......
  • [转]Windows10下CLion配置说明
    Windows10下CLion配置说明CLion是C/C++的IDE,可以配置多种编译环境,本文以配置MinGW编译环境为例。安装CLion的安装可直接到官网下载ZIP,文件解压后直接运行即可。我在......
  • Camera | 5.Linux v4l2架构(基于rk3568)
    上一篇我们讲解了如何编写基于V4L2的应用程序编写,本文主要讲解内核中V4L2架构,以及一些最重要的结构体、注册函数。厂家在实现自己的摄像头控制器驱动时,总体上都遵循这个架......
  • Java关于栈(Stack)、队列(Queue)知识点拓展
    Stack类:Stack继承了Vector,而Vector类底层使用数组存储数据,Stack对象中存储的数据也是存储在数组中,常用方法:push(Edata):把数据压入栈addElement():是父类Vect......
  • C# Logger类
    usingSystem;usingSystem.IO;publicenumLogLevel{Debug,Info,Warning,Error,Fatal}publicclassLogger{privatestring_log......
  • Docker部署gitlab(避坑版)
    1下载docker#安装依赖yuminstall-yyum-utilsdevice-mapper-persistent-datalvm2#设置yum源yum-config-manager--add-repohttps://download.docker.com/li......
  • unixODBC 不使用GUI配置数据源
     {PurposeAlotofpeopleareusingunixODBCbutforanumberofreasonsarenotbuildingtheGUIconfigurationandtestingtools(ODBCConfigandDataManage......
  • elasticsearch 排错总结
    控制台乱码修改elasticsearch-7.6.2\config下的jvm.options文件,在任意行上加上-Dfile.encoding=GBKIK报错但成功启动,按照网上的说法是jdk权限不足,修改方式是改变jdk权限......
  • Yolov5环境报错解决:No labels found in 与 Could not run 'torchvision::nms' with ar
    问题记录yolov5环境1Nolabelsfoundin(Done)报错内容F:\WorkSpace\GitSpace\yolov5>pythontrain-self.pytrain-self:weights=weights/yolov5s.pt,cfg=models/y......
  • Pandas使用时间索引筛选时报错 AssertionError: <class 'numpy.ndarray'>
    Icameacrossthesimilarproblem,mysolutionwas:makesuretheindexistypeof'DatetimeIndex',Idothis:df.index=pd.to_datetime(df.index)sortedth......