先来直接放一段代码
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 public class CoroutineTest : MonoBehaviour 6 { 7 8 void Start() 9 { 10 Debug.Log("Start Begin"); 11 12 CustomCoroutine = DelayPrint("Hi"); 13 14 Debug.Log("Start End"); 15 } 16 17 bool isStartCoroutine = true; 18 IEnumerator CustomCoroutine = null; 19 void Update() 20 { 21 Debug.Log("第"+Time.frameCount+"Update 在执行"); 22 if (isStartCoroutine) 23 { 24 if (CustomCoroutine.MoveNext()) 25 { 26 Debug.Log(CustomCoroutine.Current.ToString()); 27 } 28 } 29 Debug.Log("第" + Time.frameCount + "Update执行结束"); 30 } 31 32 public IEnumerator DelayPrint(string somevalue) 33 { 34 Debug.Log("DelayPrint Start"); 35 yield return 0; 36 Debug.Log("DelayPrint Debug:" + somevalue); 37 Debug.Log("DelayPrint End"); 38 } 39 40 }
代码结果如下:
这样一看是不是跟我们平时在Unity使用的StartCoroutine基本上是一样的了?其实还是有不一样的地方,这因为在Unity内部对自己的协程管理作了特殊处理(猜测)。
在Unity内的协程位置顺序图如下:
而我们在上面写的自己的协程只会在Update里运行,因为我们在Update里写MoveNext(),若是想在其他地方运行我们的协程就写在其他地方就好~
所以说Unity协程的本质就是Unity内部的轮询机制和IEnumerator迭代保存上下文执行代码段。
而我们平时在协程方法yield return XXX只是把这时刻代码运行的控制权交回给主线程(类似操作系统中的挂起),其他协程或者其他线程(类似WWW类,这会真正开启另一个线程去执行,否则本质上都是单线程),,然后保存当前方法代码段的状态,等Unity内部的再次轮询回到被挂起时的代码状态继续执行,直到没有yield return的时候,这样就形成了一种代码分段执行的效果。
也当只有yield return XXX时IEnumerator.MoveNext()才会为true,然后IEnumerator.Current是yield return XXX的XXX。
所以在Unity里面的yield return WaitForSeconds返回给Unity中内部的协程状态机作判断处理,例如如果是返回WaitForSeconds类则给这个协程求时间差以达到延迟处理。
如果没有yield return,也就是没有返回值,这时IEnumerator.MoveNext()为false,这就会退出了Unity轮询。
大概就这样,要是更想理解细节的话可以百度IEnumerator和作用和把上面的代码逐步调试下即可。
参考:https://www.zhihu.com/question/34878524/answer/61555725
https://www.zhihu.com/question/23895384/answer/26066323
http://dsqiu.iteye.com/blog/2029701
CoroutineManager插件源码(这个插件是在自己的Update方法里不断轮询CoroutineManager的协程集合内,获取每个协程的MoveNext()状态,从而进行一些简单的判断管理一个协程)