开启一个协程发生了什么 分析如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TestScript : MonoBehaviour { private void Start ( ) { StartCoroutine(ShowLog()); } IEnumerator ShowLog ( ) { for (int i = 0 ; i < 100 ; i++) { Debug.Log(i); yield return new WaitForSeconds (1f ) ; } } }
先描述一下里面用到的方法和类:
StartCoroutine StartCoroutine是 Monobehavior类的函数,有3个重载函数
public Coroutine StartCoroutine(string methodName); public Coroutine StartCoroutine(IEnumerator routine); public Coroutine StartCoroutine(string methodName, [DefaultValue(“null”)] object value);
StartCoroutine的第一个和第三个methodName ,都是一个返回类型是IEnumerator的方法。
所以入参全是IEnumerator迭代器方法 。返回类型全是Coroutine 类。
Coroutine 协同程序。继承自YieldInstruction 类。
StartCoroutine函数 返回 Coroutine。协同程序是一个可以暂停执行 (yield) 的函数,直到给定的 YieldInstruction 完成。
YieldInstruction yield return后面可以是值,也可以是一个类型为继承自YieldInstruction的类 。
如果yield return的是YieldInstruction的派生类,Unity就会将其理解为“持续等待”。比如WaitForEndOfFrame、WaitForFixedUpdate、WaitForSeconds、WWW、Coroutine(StartCoroutine的返回值),它们都是。
yield return yield return后面可以跟的表达式:
所有非YieldInstruction派生类(包括null):协程将会在下一帧恢复,继续后续代码。
WaitForEndOfFrame:协程将会在这一帧结束之后(所有渲染、GUI)恢复,继续后续代码。
WaitForFixedUpdate:所有物理引擎计算完成之后恢复,继续后续代码。
WaitForSeconds:等待x秒后(以Unity内的计时系统为基准)恢复,继续后续代码。
WWW:等待一个web request结束后恢复,继续后续代码。
Coroutine 其他协程(协程嵌套):等子协程Coroutine执行完后恢复,继续后续代码。如果子协程内有yield中断,那父协程会一直暂停,直到子协程运行完毕。
CustomYieldInstruction 想实现自定义和YieldInstruction
一样,拥有“持续等待”逻辑的协程,就用这个。继承自IEnumerator
类。
重写keepWaiting
函数即可。要使协同程序保持暂停,则返回true
;要使协同程序继续执行, 则返回false
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using System.Collections;using UnityEngine;public class ExampleScript : MonoBehaviour { void Update ( ) { if (Input.GetMouseButtonUp(0 )) { Debug.Log("Left mouse button up" ); StartCoroutine(waitForMouseDown()); } } public IEnumerator waitForMouseDown ( ) { yield return new WaitForMouseDown ( ) ; Debug.Log("Right mouse button pressed" ); } } public class WaitForMouseDown : CustomYieldInstruction { public override bool keepWaiting { get { return !Input.GetMouseButtonDown(1 ); } } public WaitForMouseDown ( ) { Debug.Log("Waiting for Mouse right button down" ); } }
协程原理 如果代码中对gameObject.SetActive(false)
,协程就会失效,即使再次激活,也不能继续执行。原因是协程是在StartCoroutine时被注册到的GameObject上,他的生命期受限于GameObject的生命期,因此受GameObject是否active的影响。
不难得出,协程和Update一样是在每一帧被调用执行的。经过测试,一般是在LastUpdate之后执行的。
而具体怎么执行,是利用了迭代器 :每一帧检测yield的返回情况(想想StartCoroutine估计就是while(MoveNext)
)。也就是每一帧都执行MoveNext ,如果为true下一帧就继续执行MoveNext,如果为false就结束协程将其从协程队列中剔除。
自己实现携程 // 当然也可以考虑用async-await替代协程,ETTask就是这样的。具体以后再看,粗看应该是最后将回调放到同步上下文.Post
里做了。
找到一个不错的实践,转载 一下。可以手动控制携程顺序、执行片长。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Profiling; public class QuotaCoroutine : MonoBehaviour { static float frameQuotaSec = 0.001f ; static LinkedList <IEnumerator > s_tasks = new LinkedList<IEnumerator>(); void Start ( ) { StartQuotaCoroutine(Task(1 , 100 )); } void Update ( ) { ScheduleTask(); } void StartQuotaCoroutine (IEnumerator task ) { s_tasks.AddLast(task); } static void ScheduleTask ( ) { float timeStart = Time.realtimeSinceStartup; while (s_tasks.Count > 0 ) { var t = s_tasks.First.Value; bool taskFinish = false ; while (Time.realtimeSinceStartup - timeStart < frameQuotaSec) { Profiler.BeginSample(string .Format("QuotaTaskStep, f:{0}" , Time.frameCount)); taskFinish = !t.MoveNext(); Profiler.EndSample(); if (taskFinish) { s_tasks.RemoveFirst(); break ; } } if (!taskFinish) return ; } } IEnumerator Task (int taskId, int stepCount ) { int i = 0 ; while (i < stepCount) { Debug.LogFormat("{0}.{1}, frame:{2}" , taskId, i, Time.frameCount); i++; yield return null ; } } }