前言:
1.本文代码参考了: 自己实现unity的协程功能_BlueBones_fan的博客-CSDN博客 , 在其基础上进一步模仿Unity的写法并延伸
2.本文所有代码已上传到CSDN,可以下载(所以用更清晰的图片形式展示代码)
3.本文默认读者完全了解1)IEnumerator接口2)Unity协程的日常用法,包括协程嵌套的用法.3)yield语法糖如何生成迭代器. 如还不了解请先看其他教程.
本文目的: 在不使用Unity引擎的情况下, 纯粹用C#代码在Console实现协程的效果, 并实现WaitForSeconds 这种协程指令(YeildInstruction)类, 最后还将揭示协程指令类与嵌套协程执行顺序的联系
准备工作:
首先我们知道Unity中协程的原理就是, 事件系统在生命周期中(渲染循环或物理循环)会调用所有已注册的协程一次:
那么WaitForSeconds的作用,必然就是, 当事件系统调用本协程时, 说 "你别急,KeepWaiting吧". 事件系统就走了, 等下次渲染再来问一遍...
从官方文档中可以看到, WaitForSeconds 是一个类, 它继承自:YieldInstruction(Unity中文文档称之为"Yeild指令"). 可惜这个类看不到内部实现, 好在Unity给我们提供了一个类似的(可以反编译的)类:
从这里可以猜测协程系统内部实现, 现在开始:
正式开始:
只需要引入这几个命名空间
using System;
using System.Collections;
using System.Collections.Generic;//泛型
using System.Threading;//用于模拟Unity事件系统每帧调用
首先写一个一模一样的Yeild指令类YieldInstruction
WaitForSeconds将要继承这个类,并实现KeepWaiting属性.
可以猜测其原理: 当事件系统询问它,"我KeepWaiting吗?" WaitForSeconds(float seconds)的内部将会比较TimeSpan=[现在的时间]-[自己被Yeild出来的时间] , TimeSpan超过seconds, 就回复事件系统说"KeepWaiting=false, 继续执行吧" .
所以我们必须有一个类, 用来记录WaitForSeconds这种YieldInstruction被创建时的环境参数, 也就是[自己被Yeild出来的时间], 根据小道消息, 这是MonoBehaviour类的内部类, 所以写法如下. 并且给MyWaitForSeconds写一个实现类当他的儿子
这里就选取T0泛型作为 DateTime类型, 来储存Yield 出WaitForSeconds时的时间. 使用父类是为了后面调父执行子.接下来我们继续下一个类.
既然每帧事件系统都要检查每个激活的协程, 而一个激活的协程有3个组成部分:
1) 给StartCoroutine()方法传的迭代器IEnumerator
2) 迭代器IEnumerator 中yield return 出来的yield指令类实例(比如WaitForSeconds)
3)yield指令类诞生时的环境参数类
那么这三个东西就封装在一起, 也作为MonoBehaviour的内部类
我们想要对扩展开放,对修改关闭, 就用了dynamic泛型. 储存一个静态的自身类型变量,是为了方便YieldInstruction类调用. 下一步...
现在我们可以写WaitForSeconds的实现了:
然后写StartCoroutine方法
用一个List来装协程信息, 把传入StartCoroutine()的迭代器直接装到变量routine, 执行一次这个迭代器, 看看其yield return出来的是不是YieldInstruction子类, 是的话直接填充其他2个变量, 不是就保持null.
这样, 协程信息类的3个属性就都有了, 然后装入列表. 继续...
有了协程信息列表, 就可以写Update类, 功能是遍历执行每个列表成员协程.
这个Update由事件系统调用. 遍历所有已开启协程,判断本次是否可以迭代(不能迭代就不操作).
只有一种情况本次不迭代(不执行MoveNext()), 那就是这个协程yield return了一个协程指令类YieldInstruction, 且它的KeepWaiting=true.(没有协程指令类的协程当然直接放行)
对于可以迭代的,正常执行MoveNext 并获取新的Current用来更新其[协程信息封装类obj](更新方法和开协程时一样), 当然,没有下一个可迭代则结束当前协程.
下面我们可以开始调用协程了(使用方法和Unity中基本没有区别):
结果:
嵌套协程原理:
看懂上面的代码, 就明白嵌套协程为何总是等内部迭代器先执行完毕了.
如果yield return出来的不是YieldInstruction类, 而是一个普通迭代器IEnumerator, (虽然它没有KeepWaiting属性,但)Unity会直接把IEnumerator的MoveNext当做KeepWaiting来处理. 比如说,当我们在协程中yield return 一个IEnumerator, 事件系统每帧问这个IEnumerator的MoveNext要一个值, 只要是true(代表还有下一个值可以迭代)就视为需要KeepWaiting. 这就是为什么嵌套协程总是先执行内部协程.
虽然上面写法还原了嵌套协程和WaitForSeconds, 但还是不支持3+层嵌套的协程. 想要实现这个功能, 可能需要把RoutineInfo协程信息封装类的3个属性全部改为Stack集合, 每遇到一层嵌套协程就压栈一次. 另外,Unity还实现了使用Yield指令控制协程在生命周期中被调用的时机,具体实现方法也是不得而知的.