热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

unity协程_[C#进阶]C#实现类似Unity的协程

使用过Unity的同学一定知道,Unity提供了一套协程机制,简直不要太好用。但是这个协程依赖于Unity引擎,离开Unity就无法使用。

0242c8fe09ab21460cf81dbfecddb84c.png

使用过Unity的同学一定知道,Unity提供了一套协程机制,简直不要太好用。但是这个协程依赖于Unity引擎,离开Unity就无法使用。那有没有办法实现不依赖Unity的协程呢?答案是当然阔以。 所谓实现一个协程,就是实现一个迭代器的容器!

Unity的协程

使用过Unity的同学应该都清楚Unity提供的协程,它可以使用的场景非常广泛。比如我们需要在UI打开的时候,延迟一秒钟播放一个动画。代码可以如下写:

public void OnUIOpen()
{StartCoroutine(DelyToPlayAni());
}private IEnumerator DelyToPlayAni()
{yield return new WaitForSeconds(0.5f);//PlayAniMethod
}

我们可以看到StartCoroutine方法的参数是一个返回值很奇怪的函数,这个IEnumerator 是一个什么鬼东西呢?

这要从迭代器模式说起。 迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式。简单来说,迭代器模式使得你能够获取到序列中的所有元素而不用关心是其类型是array,list,linked list或者是其他什么序列结构。这一点使得能够非常高效的构建数据处理通道(data pipeline),即数据能够进入处理通道,进行一系列的变换,或者过滤,然后得到结果。事实上,这正是LINQ的核心模式。

我们来看一下IEnumerator的接口是什么样子的。

using System.Runtime.InteropServices;namespace System.Collections
{[ComVisible(true)]public interface IEnumerator{object Current { get; }bool MoveNext();void Reset();}
}

我们发现IEnumerator属于System.Collections程序集,这说明,这个小东西根本不是Unity创造的,而是C#本身就拥有的东西。也说明,我们完全可以自己实现一个类似Unity的协程嘛!ok,摩拳擦掌,准备大展身手。

什么是IEnumerator

尴尬的问题来了,我们并不是很清楚迭代器到底是什么东西以及它的工作模式。看来开始之前,还有一些知识需要了解。

在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,他是数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。 这个只能向前移动的特性就是我们协程需要用到的特性。所谓实现一个协程,就是实现一个迭代器的容器!

早在C#1的时候,C#就提供了一个迭代器,那就是foreach。 使得能够进行比for循环语句更直接和简单的对集合的迭代,编译器会将foreach编译来调用GetEnumerator和MoveNext方法以及Current属性,如果对象实现了IDisposable接口,在迭代完成之后会释放迭代器。 我们可以再回顾下IEnumerator的接口内容,各自含义如下:

object Current{ get; } 当前正在访问的对象,只读
bool MoveNext();移动到下一个元素,我们的协程通过此函数来控制迭代器的到底要不要移动下一个代码段.
如果迭代器返回false,则证明迭代器已经执行完毕,此时协程也应该执行完毕
void Reset();重置函数

开始设计协程

根据以上分析,来我们可以设计我们的协程:需要一个迭代器对象,并且处理其接口相关的函数。

///协程类定义
public sealed class Coroutine
{private IEnumerator _routine;public Coroutine(IEnumerator routine){_routine = routine;}public bool MoveNext(){if (_routine == null)return false;return _routine.MoveNext();}public void Stop(){_routine = null;}
}

如何驱动这个协程呢?我们可以定义一个协程管理器类。

using System;
using System.Collections;
using System.Collections.Generic;namespace Coroutine
{///使用单例模式的协程管理器,用于驱动所有协程MoveNextpublic class CoroutineManager{private static CoroutineManager _instance = null;public static CoroutineManager Instance{get{if (_instance == null)_instance = new CoroutineManager();return _instance;}}///链表存储所有协程对象private LinkedList coroutineList = new LinkedList();private LinkedList coroutinesToStop = new LinkedList();///开启一个协程public Coroutine Start(IEnumerator ie){var c = new Coroutine(ie);coroutineList.AddLast(c);return c;}///关闭一个协程public void Stop(IEnumerator ie){}public void Stop(Coroutine coroutine){coroutinesToStop.AddLast(coroutine);}///主线程驱动所有协程对象public void UpdateCoroutine(){var node = coroutineList.First;while (node != null){var cor = node.Value;bool ret = false;if (cor != null){bool toStop = coroutinesToStop.Contains(cor);if (!toStop){//一旦协程对象返回false,即意味着该协程要退出ret = cor.MoveNext();}}if (!ret){coroutineList.Remove(node);Console.WriteLine("[CoroutineManager] remove cor");}node = node.Next;}}}
}

开始测试协程1.0

我们的协程1.0版本写好了,如何测试呢?简单,这里以C#控制台程序驱动我们的协程为例。不过在正式测试前,还有一些工作要做。

我们需要定义一些辅助的接口和类来模拟Unity的协程控制,如WaitForSeconds等。相关实现代码如下:

using System;
namespace Coroutine
{public class Time{public const float deltaTime &#61; 0.02f;public const int deltaMilTime &#61; 20;}public interface IWait{bool Tick();}public class WaitForSeconds : IWait{public float waitTime &#61; 0;public WaitForSeconds(float time){waitTime &#61; time;}bool IWait.Tick(){waitTime -&#61; Time.deltaTime;Console.WriteLine("[WaitForSeconds] now left:" &#43; waitTime);return waitTime <&#61; 0;}}public class WaitForFrame : IWait{public int waitFrame &#61; 0;public WaitForFrame(int frame){waitFrame &#61; frame;}bool IWait.Tick(){waitFrame--;Console.WriteLine("[WaitForFrame] now left:" &#43; waitFrame);return waitFrame <&#61; 0;}}
}

为了使得WaitForSeconds等可以参与我们协程的流程控制&#xff0c;还需要对Coroutine进行改造&#xff0c;主要改造的是MoveNext函数&#xff0c;改造完后的Coroutine完整代码如下&#xff1a;

using System;
using System.Collections;namespace Coroutine
{public class Coroutine{private IEnumerator _routine;public Coroutine(IEnumerator routine){_routine &#61; routine;}public bool MoveNext(){if (_routine &#61;&#61; null)return false;//看迭代器当前的流程控制&#xff08;即yield return 后边的对象&#xff09;//是否是我们当前IWait对象&#xff0c;如果是&#xff0c;看是否满足moveNext的条件IWait wait &#61; _routine.Current as IWait;bool moveNext &#61; true;if (wait !&#61; null)moveNext &#61; wait.Tick();if (!moveNext){//此处有些不好理解。当时间没有到时&#xff0c;我们应该返回true//告诉管理器我们后边还有对象需要下一次继续迭代Console.WriteLine("[Coroutine] not movenext");return true;}else{//此时所有等待时间或者帧都已经迭代完毕&#xff0c;看IEnumerator对象后续是否还有yield return对象//将此结果通知给管理器&#xff0c;管理器会在下一次迭代时决定是否继续迭代该Coroutine对象Console.WriteLine("[Coroutine] movenext");return _routine.MoveNext();}}public void Stop(){_routine &#61; null;}}
}

一切就绪&#xff0c;code我们控制台代码&#xff0c;在main韩素中模拟unity的update函数&#xff0c;不断update我们的协程管理器即可。测试代码如下&#xff1a;

using System;
using System.Collections;
using System.Threading;namespace Coroutine
{class MainClass{public static void Main(string[] args){Console.WriteLine("[Main] coroutine test begin");CoroutineManager.Instance.Start(CoroutineTest());int framecount &#61; 0;//模拟当前帧数,当前time下&#xff0c;1秒50帧while (true){framecount&#43;&#43;;Console.WriteLine("[Main] cur framecount:<" &#43; framecount &#43; "> CoroutineManager.Instance.UpdateCoroutine");CoroutineManager.Instance.UpdateCoroutine();//模拟unity的updateThread.Sleep(Time.deltaMilTime);if (framecount >&#61; 35)break;}}private static IEnumerator CoroutineTest(){Console.WriteLine("[CoroutineTest] enter coroutine. begin return waitforseconds:0.5f");yield return new WaitForSeconds(0.5f);Console.WriteLine("[CoroutineTest] wait for seconds <0.5f> over. begin wait for frame:3");yield return new WaitForFrame(3);Console.WriteLine("[CoroutineTest] wait for frame <3> over. now exit corutine");}}
}

由我们的协程内容可知&#xff1a;第一个yield return延迟0.5秒&#xff0c;即25帧&#xff0c;应该在第26帧执行完毕。第2个yield return延迟3帧&#xff0c;应该在第29帧执行完毕。因此35帧的时候while循环退出。我们看下控制台打印结果&#xff0c;如下&#xff1a;

Last login: Wed Feb 12 17:29:04 on ttys000[Main] coroutine test begin
[Main] cur framecount:<1> CoroutineManager.Instance.UpdateCoroutine
[Coroutine] movenext
[CoroutineTest] enter coroutine. begin return waitforseconds:0.5f
[Main] cur framecount:<2> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.48
[Coroutine] not movenext
[Main] cur framecount:<3> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.46
[Coroutine] not movenext
[Main] cur framecount:<4> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.44
[Coroutine] not movenext
[Main] cur framecount:<5> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.42
[Coroutine] not movenext
[Main] cur framecount:<6> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.3999999
[Coroutine] not movenext
[Main] cur framecount:<7> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.3799999
[Coroutine] not movenext
[Main] cur framecount:<8> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.3599999
[Coroutine] not movenext
[Main] cur framecount:<9> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.3399999
[Coroutine] not movenext
[Main] cur framecount:<10> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.3199999
[Coroutine] not movenext
[Main] cur framecount:<11> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.2999999
[Coroutine] not movenext
[Main] cur framecount:<12> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.2799999
[Coroutine] not movenext
[Main] cur framecount:<13> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.2599999
[Coroutine] not movenext
[Main] cur framecount:<14> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.2399999
[Coroutine] not movenext
[Main] cur framecount:<15> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.2199999
[Coroutine] not movenext
[Main] cur framecount:<16> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.1999999
[Coroutine] not movenext
[Main] cur framecount:<17> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.1799999
[Coroutine] not movenext
[Main] cur framecount:<18> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.1599999
[Coroutine] not movenext
[Main] cur framecount:<19> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.1399999
[Coroutine] not movenext
[Main] cur framecount:<20> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.1199999
[Coroutine] not movenext
[Main] cur framecount:<21> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.0999999
[Coroutine] not movenext
[Main] cur framecount:<22> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.07999991
[Coroutine] not movenext
[Main] cur framecount:<23> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.05999991
[Coroutine] not movenext
[Main] cur framecount:<24> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.03999991
[Coroutine] not movenext
[Main] cur framecount:<25> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:0.01999991
[Coroutine] not movenext
[Main] cur framecount:<26> CoroutineManager.Instance.UpdateCoroutine
[WaitForSeconds] now left:-8.940697E-08
[Coroutine] movenext
[CoroutineTest] wait for seconds <0.5f> over. begin wait for frame:3
[Main] cur framecount:<27> CoroutineManager.Instance.UpdateCoroutine
[WaitForFrame] now left:2
[Coroutine] not movenext
[Main] cur framecount:<28> CoroutineManager.Instance.UpdateCoroutine
[WaitForFrame] now left:1
[Coroutine] not movenext
[Main] cur framecount:<29> CoroutineManager.Instance.UpdateCoroutine
[WaitForFrame] now left:0
[Coroutine] movenext
[CoroutineTest] wait for frame <3> over. now exit corutine
[CoroutineManager] remove cor
[Main] cur framecount:<30> CoroutineManager.Instance.UpdateCoroutine
[Main] cur framecount:<31> CoroutineManager.Instance.UpdateCoroutine
[Main] cur framecount:<32> CoroutineManager.Instance.UpdateCoroutine
[Main] cur framecount:<33> CoroutineManager.Instance.UpdateCoroutine
[Main] cur framecount:<34> CoroutineManager.Instance.UpdateCoroutine
[Main] cur framecount:<35> CoroutineManager.Instance.UpdateCoroutinePress any key to continue...

控制台打印的结果可以佐证我们的协程执行符合预期&#xff0c;WaitForSeconds和WaitForFrame可以正常的控制我们的协程的流程&#xff01;nice&#xff01;我们的第一课内容虽然多&#xff0c;但是出师大捷&#xff0c;为我们的协程的扩展打下了坚实的基础&#xff01;



推荐阅读
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了MVP架构模式及其在国庆技术博客中的应用。MVP架构模式是一种演变自MVC架构的新模式,其中View和Model之间的通信通过Presenter进行。相比MVC架构,MVP架构将交互逻辑放在Presenter内部,而View直接从Model中读取数据而不是通过Controller。本文还探讨了MVP架构在国庆技术博客中的具体应用。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了将mysql从5.6.15升级到5.7.15的详细步骤,包括关闭访问、备份旧库、备份权限、配置文件备份、关闭旧数据库、安装二进制、替换配置文件以及启动新数据库等操作。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • ECMA262规定typeof操作符的返回值和instanceof的使用方法
    本文介绍了ECMA262规定的typeof操作符对不同类型的变量的返回值,以及instanceof操作符的使用方法。同时还提到了在不同浏览器中对正则表达式应用typeof操作符的返回值的差异。 ... [详细]
  • 在C#中,使用关键字abstract来定义抽象类和抽象方法。抽象类是一种不能被实例化的类,它只提供部分实现,但可以被其他类继承并创建实例。抽象类可以用于类、方法、属性、索引器和事件。在一个类声明中使用abstract表示该类倾向于作为其他类的基类成员被标识为抽象,或者被包含在一个抽象类中,必须由其派生类实现。本文介绍了C#中抽象类和抽象方法的基础知识,并提供了一个示例代码。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文详细介绍了cisco路由器IOS损坏时的恢复方法,包括进入ROMMON模式、设置IP地址、子网掩码、默认网关以及使用TFTP服务器传输IOS文件的步骤。 ... [详细]
author-avatar
山人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有