Unity 協程等待物件的生命週期
Unity系統提供的協程等待有下面幾個 :
yield return new WaitForFixedUpdate(); yield return new WaitForEndOfFrame(); yield return new WaitForSeconds(1.0f); yield return new WaitForSecondsRealtime(1.0f);
一般情況下我們對協程的生命週期不是很關心, 因為是非同步模式的 ( 雖然它不是非同步的 ), 不過對於一個需要返回的等待物件, Unity 提供的是一個 class 物件, 比如一個迴圈等待的協程, 它就會產生很多的 GC 物件, 像下面這樣 :
IEnumerator TestCoroutine1() { while(true) { // ...... yield return new WaitForEndOfFrame(); // ...... } }
而它又只是一個物件, 沒有附帶其它資訊, 那麼這個東西其實是可以複用的, 直接給它一個靜態物件也是一樣的 :
public static readonly WaitForEndOfFrame WaitForEndOfFrame = newWaitForEndOfFrame(); IEnumerator TestCoroutine1() { while(true) { yield return WaitForEndOfFrame; Debug.Log(Time.frameCount); } }
然後很多人發現對於 WaitForEndOfFrame 其實返回的物件是空時 ( null ), 得到的結果也一樣, 可是其實他們執行程式碼所在的生命週期是不同的, 可以看看下面的測試結果 :
using System;using System.Collections; using System.Collections.Generic; using UnityEngine; public class Test : MonoBehaviour { int id = 0; void Awake() { StartCoroutine(TestCoroutine()); } void Update() { Debug.Log("Update :: " + id++); } void LateUpdate() { Debug.Log("LateUpdate :: " + id++); } private void OnPostRender() { Debug.Log("OnPostRender :: " + id++); } IEnumerator TestCoroutine() { Debug.Log("Coroutine Start :: " + id++); int i = 0; while(i++ < 3) { Debug.Log("Coroutine Tick : " + i + " :: " + id++); yield return null; Debug.Log("Coroutine End : " + i + " :: " + id++); } this.enabled = false; } }
這裡使用 null 作為協程返回, 添加了 Update, LateUpdate, OnPostRender 等生命週期, 得到的結果如下 :
可見每次 yield 等待之後的程式碼呼叫都是在Update之後, LateUpdate之前呼叫的, 它插入的生命週期就在這裡, 而WaitForEndOfFrame 的就不同了, 就像描述的一樣, 是在"一幀"之後才會呼叫 :
IEnumerator TestCoroutine() { Debug.Log("Coroutine Start :: " + id++); int i = 0; while(i++ < 3) { Debug.Log("Coroutine Tick : " + i + " :: " + id++); yield return new WaitForEndOfFrame(); Debug.Log("Coroutine End : " + i + " :: " + id++); } this.enabled = false; }
可以看到插入的生命週期在相機完成渲染之後, 其實是在所有生命週期之後, 所以需要根據實際需求選擇正確的返回, 比如你要獲取當前幀相機的正確渲染結果, 就需要使用 WaitForEndOfFrame, 而你如果想要改變當前幀的渲染結果, 你就要使用 null 返回, 否則就變成獲取到上一幀的渲染結果和改變下一幀的渲染了, 雖然影響不大...
而 WaitForFixedUpdate 這個在物理幀裡的顯然使用的就很少了, 它跟邏輯跟渲染都不搭嘎.
而WaitForSeconds 跟WaitForSecondsRealtime 有個決定性的不同,WaitForSeconds 跟WaitForEndOfFrame 一樣只是個簡單物件, 而WaitForSecondsRealtime 有自己的計時器 :
所以測試結果 WaitForSecondsRealtime 是無法使用全域性物件的 :
IEnumerator TestCoroutine() { var WaitForSecondsRealtime = new WaitForSecondsRealtime(1.0f); Debug.Log("Coroutine Start :: " + id++); int i = 0; while(i++ < 3) { Debug.Log("Coroutine Tick : " + i + " :: " + id++); yield return WaitForSecondsRealtime; Debug.Log(Time.realtimeSinceStartup); Debug.Log("Coroutine End : " + i + " :: " + id++); } this.enabled = false; }
它在第一次成功返回之後, 每幀都是成功返回, 沒有意義...
而 WaitForSeconds 還是正常的 :
IEnumerator TestCoroutine() { var WaitForSeconds = new WaitForSeconds(1.0f); Debug.Log("Coroutine Start :: " + id++); int i = 0; while(i++ < 3) { Debug.Log("Coroutine Tick : " + i + " :: " + id++); yield return WaitForSeconds; Debug.Log(Time.realtimeSinceStartup); Debug.Log("Coroutine End : " + i + " :: " + id++); } this.enabled = false; }