【Unity】协程底层机制,嵌套协程原理,WaitForSeconds等YeildInstruction原理

前言:

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指令控制协程在生命周期中被调用的时机,具体实现方法也是不得而知的.

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值