Unity中的coroutine是通過yield expression;來實現的。官方腳本中到處會看到這樣的代碼。
疑問:
yield是什么?
Coroutine是什么?
unity的coroutine程序執行流程怎么那么奇怪?
unity中的coroutine原理是什么,怎么實現的?
使用unity的coroutine需要注意什么問題?
一、yield的在幾種語言中的程序執行特性:
Lua中的yield是使得協同函數運行->掛起並且傳遞參數給resume。resume使得協同函數掛起->運行並且傳遞參數給協同函數。
C#中yield return/break是用於函數查詢集合生成器里面的值(類似迭代)返回,並記錄當前現場,下次查詢時從上一次記錄的yield現場處,繼續往下執行,直到繼續往下執行沒有了,那么退出這段yield的邏輯。yield break會終止掉yield迭代邏輯並跳出。
YieldImplementation:
1).Caller callsfunction
2).Caller requestsitem 按需請求一個元素
3).Next itemreturned 返回請求的元素
4).Goto step #2
Python中的yield expression, 有yield的函數就變成了一個生成器,調用該函數返回的是迭代器對象,用迭代器對象調用next方法(或者循環中會自動調用next方法),才開始執行函數,執行到yield expression處則中斷,返回迭代器當前的值,並保留現場,下次調用next則從現場處開始執行,迭代完了就停止了。可以看出Python中的yield和C#中的yield是類似的,用於創建生成器,執行時中斷返回迭代器值,並記錄現場,下次從現場處繼續執行。
Unity中的yield就是和C#,python中的類似,因為unity是基於.net框架的,且unity腳本開始是用Boo(Python的一個變種)寫的。只是unity中多了coroutine特性類型,和StartCoroutine的coroutine管理類。StartCoroutine不是啟動了一個新的線程,而是開啟一個協同程序,默認unity所有代碼都在一個線程中(http://answers.unity3d.com/questions/280597/new-thread-vs-startcoroutine.html)。
coroutine語言層面的原理:
在兩年前,協程似乎是一個很高級的東西,隨后大多數語言或多或少都支持協程。我比較熟悉的有Python的gevent,Lua的coroutine,Go的goroutine。尤其是Lua和Go,語言本身就支持協程。協程也被叫做輕量級線程。通俗點講就是定義一大堆任務,然后通過一個線程輪着對每個任務都執行一下,協作運行。它的厲害之處在於每運行到一個任務的時候,它都可以從這個任務上一次中斷的地方開始運行。在我們一般的印象中,只有操作系統對線程進行調度的時候才會干這樣的事情,進行各種進棧,保存狀態。而協程,總共也只是運行在一個線程中,要是使用線程本身的系統棧,早就暴了。因此在這里,實現的時候是用內存來模擬棧的操作。具體實現,我想復雜度一定會不小。
我們知道,線程比進程輕量級,因此產生一個線程消耗的資源比進程少,上下文切換也比進程節約。而協程比線程更加輕量級,上下文切換更是迅速。於是在服務器編程方面給人無限想象。盡管目前還沒有出現一款主流的采用協程的web服務器。但是Go語言開發的web服務的性能已經嶄露頭角了。
二、Unity的Coroutine執行現象:
第一種方法:
voidStart() { print("Starting " +Time.time); StartCoroutine(WaitAndPrint(2)); print("Done " +Time.time); } IEnumerator WaitAndPrint(float waitTime) { yield return new WaitForSeconds(waitTime); print("WaitAndPrint " + Time.time); }
該段代碼的執行順序是12435
執行到4協程注冊了事件,控制權交給外部線程;外部線程執行到3;事件發生,程序分段執行機制goto到協程處記錄了堆棧信息執行5語句。
// Use this for initialization void Start() { StartCoroutine(IEnumeratorStart()); } // Update is called once per frame void Update() { } IEnumerator IEnumeratorStart() { print("Starting " + Time.time); yield return StartCoroutine(WaitAndPrint(2.0F)); print("Done " + Time.time); } IEnumerator WaitAndPrint(float waitTime) { yield return new WaitForSeconds(waitTime); print("WaitAndPrint " + Time.time); }
該段代碼的執行順序是12453
Why?這么奇怪的執行方式。
程序執行到4,執行yield return表達式注冊事件交出控制權給外部,因為外部還要交出控制權也需要執行yield return后面的表達式語句因此會重入WaitAndPrint函數接着協程當前狀態下一步執行所以執行到5,yield return 后面表達式語句執行完畢控制權完全交出,之后才執行3,根本原因是yield return 不能直接嵌套后面需要跟一個表達式(事件)。
五、Unity中使用Coroutine需要注意的問題:
1.使用的地方和不能使用的地方:
必須在MonoBehaviour或繼承於MonoBehaviour的類中調用 yield coroutine。yield不可以在Update或者FixedUpdate里使用。
2.開啟協程:
StartCoroutine(string methodName)和StartCoroutine(IEnumeratorroutine)都可以開啟一個協程,
區別:
使用字符串作為參數時,開啟協程時最多只能傳遞一個參數,並且性能消耗會更大一點; 而使用IEnumerator 作為參數則沒有這個限制。
3.刪除協程:
1).在Unity3D中,使用StopCoroutine(stringmethodName)來終止該MonoBehaviour指定方法名的一個協同程序,使用StopAllCoroutines()來終止所有該MonoBehaviour可以終止的協同程序。
包括StartCoroutine(IEnumerator routine)的。
2).還有一種方法可以終止協同程序,即將協同程序所在gameobject的active屬性設置為false,當再次設置active為ture時,協同程序並不會再開啟;
如是將協同程序所在腳本的enabled設置為false則不會生效。
4.js和C#中使用區別:
在C#中要使用 yield return而不是yield。
C#中yield(中斷)語句必須要在IEnumerator類型里,C#方法的返回類型為IEnumerator,返回值如(eg:yield return new WaitForSeconds(2); 或者 yield returnnull);