自己實現協程調度有幾個好處:
- 脫離Unity獨立,拿到別的地方也可以用。
- 非主線程也可以啟動協程,然后在主線程執行,比如異步網絡消息等。
- 可以給每個協程一個id,通過id隨時啟動或關閉某個特定的協程,或者非MonoBehavior對象也可以管理屬於自己的協程。
Unity中,Coroutine是在LateUpdate執行的,每一個update都會執行一部分代碼,拿IEnumerator來說,就是每一次都會MoveNext一下。
IEnumerator有三個接口:
- Current:返回一個object,可以設置當前的一個狀態。
- MoveNext:返回true表示沒有到最后,返回false表示已經完成枚舉。
- Reset:恢復狀態,從頭開始枚舉。
IEnumerator方法內部會有一個狀態機的實現 ,如果StartCoroutine一個返回IEnumerator方法,每次就會MoveNext到一個yield,遇到yield return,MoveNext會返回true,Current值就是return的對象(yield return null時Current=null, yield return obj時Current=obj),遇到yield break,MoveNext會返回false,表示已經執行完畢。但是如果直接調用IEnumerator.MoveNext,不會去對里面的另一個協程MoveNext,下次就跳過了。
基於以上內容,可以自己寫一個方法對IEnumerator進行MoveNext,以實現自制協程的核心代碼:
public static bool MoveNext(IEnumerator subTask) { var child = subTask.Current; //yield return另一個協程:遞歸MoveNext if (child != null && child is IEnumerator && MoveNext(child as IEnumerator)) return true; #if UNITY //yield return www:等待www完成 if(child is UnityEngine.WWW && !(child as UnityEngine.WWW).isDone) return true; #endif if (subTask.MoveNext ()) return true; return false; }
管理協程很簡單,用一個鏈表來管理:
LinkedListNode<IEnumerator> node = m_taskList.First; LinkedListNode<IEnumerator> tempNode = node; while (node != null) { if (!MoveNext(node.Value)) {
tempNode = node; node = node.Next; //此處可以寫刪除節點的后續處理 m_taskList.Remove(tempNode); } else { node = node.Next; }
}
自己寫IEnumerator方法時,return另一個協程不用寫yield return StartCoroutine(func()),直接寫yield return func()就可以,如:
IEnumerator a() { ... } IEnumerator b() { ... yield return a(); ... }
也可以自己寫一些實用的IEnumerator類,比如我們項目里用的WaitForEvent, 還有組合類如WaitForAll,WaitForAny等。