翼度科技»论坛 编程开发 .net 查看内容

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

7

主题

7

帖子

21

积分

新手上路

Rank: 1

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

Await的目标
await的目标是一个可等待对象, 而拥有GetAwaiter方法并且该方法拥有合适返回值的目标即可称为可等待对象(暂时你还不需要知道返回值需要符合什么规则,待会儿Studio会告诉你)
首先,我们通过一些简单的代码来模拟Unity的Slider, 并且实现一个方法来模拟ui动画, 然后在实例化它来看看效果
  1. public class Slider
  2. {
  3.     public void ValueTo(int value)
  4.     {
  5.         Task.Run(async () =>
  6.         {
  7.             Console.Write("HP: ");
  8.             for (int i = 0; i < value; i++)
  9.             {
  10.                 await Task.Delay(350);
  11.                 Console.Write("■");
  12.             }
  13.             Console.Write("\n");
  14.         });
  15.     }
  16. }
复制代码

效果不错, 现在你希望可以await slider.ValueTo 方法, 而不是再通过使用回调函数传来传去来监听如动画是否结束
首先我们知道await的目标是一个拥有GetAwaiter方法的对象
简单点, 我们令Slider本身是可等待的, 也即我们需要在Slider上实现一个GetAwaiter方法, 已知且其返回值有一定的限制, 我们假设其返回值为SliderAwaiter
  1. public class SliderAwaiter
  2. {
  3. }
  4. public class Slider
  5. {
  6.     public SliderAwaiter GetAwaiter()
  7.     {
  8.         SliderAwaiter awaiter = new SliderAwaiter();
  9.         return awaiter;
  10.     }
  11.     public Slider ValueToAsync(int value)
  12.     {
  13.         /*
  14.          * Task是可等待的, 这里直接返回Task即可在这里达成我们的目标
  15.          * 但本文的主要讨论的是实现 自己 的异步方法
  16.          * 并且UI动画你可以以此法逃课, 但播放动画就不好逃了
  17.          */
  18.         Task.Run(async () =>
  19.         {
  20.             Console.Write("HP: ");
  21.             for (int i = 0; i < value; i++)
  22.             {
  23.                 await Task.Delay(350);
  24.                 Console.Write("■");
  25.             }
  26.             Console.Write("\n");
  27.         });
  28.         return this;
  29.     }
  30. }
复制代码
当我们尝试在Main中直接await ValueToAsync方法时, 编译器报错(“Program.SliderAwaiter”未包含“IsCompleted”的定义), 我们进行修补。又报错(“Program.SliderAwaiter”不实现“INotifyCompletion”), 继续修, 最后一个报错(“Program.SliderAwaiter”未包含“GetResult”的定义), 补上
经过修补, 我们得到这样的SliderAwaiter
  1. public class SliderAwaiter : INotifyCompletion
  2. {
  3.     public bool IsCompleted => false;
  4.     public void OnCompleted(Action continuation)
  5.     {
  6.     }
  7.     public void GetResult() { }
  8. }
复制代码
直接await ValueToAsync测试,发现Slider表现正常, 但await并没有触发
这里暂时先直接告诉你答案, 你需要手动触发OnCompleted传入的continuation来触发await后的代码
简单改造一下SliderAwaiter和Slider得到下面的代码
  1. public class SliderAwaiter : INotifyCompletion
  2. {
  3.     public bool IsCompleted => false;
  4.     public Action onFinish;
  5.     public void OnCompleted(Action continuation)
  6.     {
  7.         if (IsCompleted)
  8.         {
  9.             continuation?.Invoke();
  10.             onFinish?.Invoke();
  11.         }
  12.         else
  13.         {
  14.             onFinish += continuation;
  15.         }
  16.     }
  17.     /// <summary>
  18.     /// 手动触发 continuation
  19.     /// </summary>
  20.     public void ReportResult()
  21.     {
  22.         onFinish?.Invoke();
  23.     }
  24.     public void GetResult() { }
  25. }
  26. public class Slider
  27. {
  28.     SliderAwaiter awaiter;
  29.     public SliderAwaiter GetAwaiter()
  30.     {
  31.         SliderAwaiter awaiter = new SliderAwaiter();
  32.         this.awaiter = awaiter;
  33.         return awaiter;
  34.     }
  35.     public Slider ValueToAsync(int value)
  36.     {
  37.         Task.Run(async () =>
  38.         {
  39.             Console.Write("HP: ");
  40.             for (int i = 0; i < value; i++)
  41.             {
  42.                 await Task.Delay(350);
  43.                 Console.Write("■");
  44.             }
  45.             Console.Write("\n");
  46.             awaiter?.ReportResult();
  47.         });
  48.         return this;
  49.     }
  50. }
复制代码
在Main函数中测试一下, 神奇的事情发生了, await后的代码成功且正确地执行了
你可能觉得有些奇妙, 你只是手动触发了OnCompleted传入的后续操作(类似Task.ContinueWith)就成功触发了结束等待, 你可能也会尝试在ReportResult中多次执行continuation, 但await的后续代码依旧只执行一次
OnCompleted中的continuation是什么, 它做了什么
了解他之前, 你可能需要先了解await是如何运行的, 首先你需要知道await的实现是基于状态机
简单点, 我们把测试代码改的简单点
  1. static async Task Main(string[] args)
  2. {
  3.     Console.WriteLine("before await");
  4.     await Task.Delay(500);
  5.     Console.WriteLine("after  await");
  6.     Console.Read();
  7. }
复制代码
然后通过dnSpy看看生成的中间语言是什么样的, 从下图和标注你应该可以清晰地看出程序的整体走向

通过简单测试代码的中间语言我们了解了await的大致走向, 那么回到最初的问题,continuation究竟做了什么
还原我们刚刚模拟Unity Slider的代码, 继续借助dnSpy, 二话不说找到ReportResult方法并且打上断点, 看看他做了什么

是的!一进来就发现一个惊喜, 第一步就跳转到一个叫做MoveNext的方法, 最终经过一番闪转腾挪我们又回到了的状态机里!
我们可以通过手动执行continuation来手动触发我们自己的awaiter!
至此, 你应该了解了如何实现自己的异步方法, 并且它不用依赖Task, TaskCompletionSource
 
最后附上完整的测试代码, 希望本片文章对你的异步之旅有所帮助
  1. internal class Program
  2. {
  3.     public class SliderAwaiter : INotifyCompletion
  4.     {
  5.         public bool IsCompleted => false;
  6.         public Action onFinish;
  7.         public void OnCompleted(Action continuation)
  8.         {
  9.             if (IsCompleted)
  10.             {
  11.                 continuation?.Invoke();
  12.                 onFinish?.Invoke();
  13.             }
  14.             else
  15.             {
  16.                 onFinish += continuation;
  17.             }
  18.         }
  19.         /// <summary>
  20.         /// 手动触发 continuation
  21.         /// </summary>
  22.         public void ReportResult()
  23.         {
  24.             onFinish?.Invoke();
  25.         }
  26.         public void GetResult() { }
  27.     }
  28.     public class Slider
  29.     {
  30.         SliderAwaiter awaiter;
  31.         public SliderAwaiter GetAwaiter()
  32.         {
  33.             SliderAwaiter awaiter = new SliderAwaiter();
  34.             this.awaiter = awaiter;
  35.             return awaiter;
  36.         }
  37.         public Slider ValueToAsync(int value)
  38.         {
  39.             Task.Run(async () =>
  40.             {
  41.                 Console.Write("HP: ");
  42.                 for (int i = 0; i < value; i++)
  43.                 {
  44.                     await Task.Delay(350);
  45.                     Console.Write("■");
  46.                 }
  47.                 Console.Write("\n");
  48.                 awaiter?.ReportResult();
  49.             });
  50.             return this;
  51.         }
  52.     }
  53.     static async Task Main(string[] args)
  54.     {
  55.         Slider slider = new Slider();
  56.         Console.WriteLine("[ START ] PLAY UI_ANIMATION");
  57.         await slider.ValueToAsync(4);
  58.         Console.WriteLine("[  END  ] PLAY UI_ANIMATION");
  59.     }
复制代码
 

来源:https://www.cnblogs.com/LaNashTT/archive/2023/03/02/17173471.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具