Coroutine In Unity
看了很多关于Unity 协程的文章,自己来总结一下.
Unity 中的什么周期
链接:https://docs.unity3d.com/Manual/ExecutionOrder.html
上图其实可以看到,yield waitforEndOfFrame
是在帧结束的时候,WaitForFixedUpdate
是在所有物理更新结束之后,其他的都在update之后,LateUpdate之前更新.
Unity协程的本质
Unity中协程并不是线程,只是Unity中的另一条逻辑,规定了相应的时间段然后响应某些事件.协程内部其实是一个迭代器
. 如上面的生命周期,每一帧都会检测协程是否满足迭代器中MoveNext
的条件,当满足的时候 就回去执行下一个条件,当没后面没有 那么就协程结束.例如下面的代码
1 | private IEnumerator Start() |
打印结果
可以看到一开始是协程优先,然后是update,可是后面总是update更新后才执行协程.
不管我们使用WaitForSeconds
WaitForSecondsRealtime
或者其他都是在每一帧中进行检查,如果满足迭代器中的MoveNext()
就会向下执行.
我们再看看WaitForSeconds
(其他也类似)其实这个类只是记住了一个时间点,通过ILSpy,我们可以看到其代码实现很简单
内部维护了一个时间节点,用于在每一帧检测当前时间点与开始的时间点是否是规定的时间差,那么在执行MoveNext
引用一下别人的话
提示下:YieldInstruction是一个类 不是一个接口
协程必须使用
StartCoroutine
,来启动,通过这个函数,创建了Coroutine
对,该对象保存了yield return
展开后的IEnumerator对象指针、MoveNext
和Current
的函数指针、结束后应当唤醒的协程的指针、指向调用者Monobehaviour
的指针等等,并将该对象coroutine
保存到该Monobehaviour
的活跃协程列表中。然后立即调用了coroutine.Run()
。coroutine.Run()
首先尝试调用InvokeMoveNext
,若发现当前协程执行完成,则会尝试调用应当唤醒的协程,否则才真正执行MoveNext
,获得返回值monoWait
。
根据返回值
monoWait
的类型,进行不同的处理。通常是传递不同的参数给CallDelayed
函数。对于返回值是Coroutine
类型(c#那边用了协程嵌套),会将这个返回值的结束后应唤醒的协程的指针指向当前的coroutine
。笔者这里发现了一种不太常见的用法:当返回值为IEnumerator
类型(c#那边没有用StartCoroutine去开启嵌套协程,而是直接在yield return 后调用)时,Unity会自动为其创建一个Coroutine
对象并初始化,效果同样。
CallDelayed
函数传入了运行协程对象的方法、qingli协程对象的方法、清理条件等。函数内部创建了一个Callback
对象,加到了全局的DelayedCallManager
的列表中。游戏主循环会在每一帧调用DelayedCallManager.Update
,在满足一定条件时(比如对应的Monobehaviour对象还没被销毁等)调用Callback
对象的方法。
所以,事情其实也就变的简单了,通过StartCoroutine来启动一个协程,提交到Unity内部更新机制中,每一帧来检测是否满足设定的条件,执行迭代器中的MoveNext
When you make a call to
StartCoroutine(IEnumerator)
you are handing the resulting IEnumerator to the underlying unity engine.
StartCoroutine()
builds aCoroutine
object, runs the first step of the IEnumerator and gets the first yielded value. That will be one of a few things, either “break”, someYieldInstruction
like"Coroutine", "WaitForSeconds", "WaitForEndOfFrame", "WWW"
, or something else unity doesn’t know about. The Coroutine is stored somewhere for the engine to look at later.… At various points in the frame, Unity goes through the stored Coroutines and checks the Current value in their IEnumerators.
WWW
- after Updates happen for all game objects; check the isDone flag. If true, call the IEnumerator’s MoveNext() function;WaitForSeconds
- after Updates happen for all game objects; check if the time has elapsed, if it has, call MoveNext();null or some unknown value
- after Updates happen for all game objects; Call MoveNext()WaitForEndOfFrame
- after Render happens for all cameras; Call MoveNextMoveNext returns false if the last thing yielded was “break” of the end of the function that returned the IEnumerator was reach. If this is the case, unity removes the IEnumerator from the coroutines list.
协程中的优化
协程其实不会很占用资源 GC,只是平时要注意写法,如果是如下的写法 那么就很坑了
1 | private IEnumerator Start() |
上面 我每次都是要去new 一个waitforSecond,那么就会慢慢产生很多的GC
看下这几个文章
C# 协程 WaitForSeconds产生GC(Garbage Collection)问题 https://blog.csdn.net/u010019717/article/details/44948903
关于Unity Corotine协程的优化 https://blog.csdn.net/qq_34106090/article/details/88834260
[Unity 协程运行时的监控和优化] https://blog.uwa4d.com/archives/USparkle_Coroutine.html
参考链接
- Unity核心原理(1)生命周期 https://zhuanlan.zhihu.com/p/55287195
- Coroutine,你究竟干了什么 https://blog.csdn.net/tkokof1/article/details/11842673
- C#迭代器——由foreach说开去 https://blog.csdn.net/u013477973/article/details/65635737
- C#基础知识—迭代器与Foreach语句 https://www.cnblogs.com/3xiaolonglong/p/9608281.html
- Unity中关于Coroutine与Async的使用问题 https://www.jianshu.com/p/86779d2ebeaa
- Unity 协程原理逆向解析 https://zhuanlan.zhihu.com/p/115172019
- 浅析unity/xlua中的协程实现 https://zhuanlan.zhihu.com/p/47573713
- 深入剖析Unity协程 https://zhuanlan.zhihu.com/p/82798132