Unity3D中的Coroutine具體解釋
本文太亂,推薦frankjfwang的:全面解析Coroutine技術
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,語言本身就支持協程。協程也被叫做輕量級線程。
通俗點講就是定義一大堆任務。然後通過一個線程輪著對每一個任務都執行一下,協作執行。它的厲害之處在於每執行到一個任務的時候。它都能夠從這個任務上一次中斷的地方開始執行。
在我們一般的印象中,僅僅有操作系統對線程進行調度的時候才會幹這種事情,進行各種進棧,保存狀態。
而協程,總共也僅僅是執行在一個線程中。要是使用線程本身的系統棧,早就暴了。因此在這裏,實現的時候是用內存來模擬棧的操作。詳細實現,我想復雜度一定會不小。
我們知道,線程比進程輕量級,因此產生一個線程消耗的資源比進程少,上下文切換也比進程節約。而協程比線程更加輕量級。上下文切換更是迅速。
於是在server編程方面給人無限想象。雖然眼下還沒有出現一款主流的採用協程的webserver。可是Go語言開發的web服務的性能已經嶄露頭角了。
二、Unity的Coroutine運行現象:
第一種方法:
voidStart()
{
print("Starting " +Time.time);----------------------------------------1
StartCoroutine(WaitAndPrint(2));-------------------------------------2
print("Done " +Time.time);-------------------------------------------3
}
IEnumerator WaitAndPrint(float waitTime)
{
yield return new WaitForSeconds(waitTime);------------------------4
print("WaitAndPrint " + Time.time);----------------------------------5
}
該段代碼的運行順序是12435
運行到4協程註冊了事件,控制權交給外部線程。外部線程運行到3。事件發生,程序分段運行機制goto到協程處記錄了堆棧信息運行5語句。
IEnumerator Start()
{
print("Starting " +Time.time);----------------------------------------1
yield return StartCoroutine(WaitAndPrint(2.0F));------------------------2
print("Done " +Time.time);------------------------------------------3
}
IEnumerator WaitAndPrint(float waitTime)
{
yield return new WaitForSeconds(waitTime);----------------------------4
print("WaitAndPrint " + Time.time);-----------------------------------------5
}
該段代碼的運行順序是12453
Why?這麽奇怪的運行方式。
程序運行到4。運行yield return表達式註冊事件交出控制權給外部,由於外部還要交出控制權也須要運行yield return後面的表達式語句因此會重入WaitAndPrint函數接著協程當前狀態下一步運行所以運行到5,yield return 後面表達式語句運行完成控制權全然交出,之後才運行3,根本原因是yield return 不能直接嵌套後面須要跟一個表達式(事件)。
三、Unity官方文檔對coroutine的解釋:
Normal coroutine updates are run after theUpdate function returns. Acoroutine is a function that can suspend its execution (yield) until the givenYieldInstruction finishes. Different uses of Coroutines:
yield; The coroutine will continue after all Update functionshave been calledon the next frame.
yield WaitForSeconds(2); Continueafter a specified time delay, after all Update functions have been called for theframe.
yield WaitForFixedUpdate(); Continue afterall FixedUpdate has been called on all scripts.
yield WWWContinue aftera WWW download has completed
yield StartCoroutine(MyFunc); Chains the coroutine, and will wait for the MyFunc coroutine to completefirst.
C#要在yield coroutine之間加上returnkeyword。
四、Unity中的Coroutine原理推測:
虛擬機分段運行機制。 同類型嵌套用棧存放實現串行運行:.NET虛擬機在每一幀循環中, 會依次進入每一個編譯器提前定義好的入口。對於Coroutine類型,編譯器須要產生一些代碼。在Coroutine類型指定的時間或事件完畢後(.net的虛擬機用函數指針進行標記管理現場和在流程中每幀檢查時間或者事件滿足後發送消息,將cpu全部權交給yield中斷的現場。或是通過包括不同Coroutine叠代器的多個管理類管理各個coroutine, 每幀用coroutine子類通過多態檢查時間或事件到達。將cpu全部權交給coroutine子類中斷的現場)。從yield中斷後的代碼處繼續往下運行, 這樣就形成了我們看到的一個function能分段運行的機制。
而對於嵌套Coroutine類型,會串行的運行而不是並行的,可能.net虛擬機對於同coroutine類型用棧存放,棧頂的先運行,從而實現串行運行,假設外層的不使用yield return,那麽不會串行運行,而是並行運行。
於是就能夠解釋上面樣例中的運行次序問題。
原理圖:
見:http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/
事實上yield WaitForSeconds/null/WaitForEndOfFrame都是通過整個遊戲While循環來驅動的,Yield僅僅是交出控制權給外部主線程且Coroutine設置事件掛起Coroutine協程(不是線程僅僅是分段運行機制)。當事件發生時候(不用下一幀到達。可是要在固定時間段內Update前或者後或Render後,超過了就會到下一幀)則進入協程裏面的代碼。
五、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);
5.協程函數返回值和參數類型。組合的設計模式:
協同程序的返回類型為Coroutine類型。在Unity3D中,Coroutine類繼承於YieldInstruction,所以,協同程序的返回類型僅僅能為null、等待的幀數(frame)以及等待的時間。
協同程序的參數不能指定ref、out參數。可是,我們在使用WWW類時會常常使用到協同程序。因為在協同程序中不能傳遞參數地址(引用),也不能輸出對象。
這使得每下載一個WWW對象都得重寫一個協同程序。解決問題的方法是建立一個基於WWW的類(用組合模式來解決-事實上就是不通過函數傳參全局關聯一個對象了),並實現一個下載方法。例如以下:
using UnityEngine;
using System.Collections;
public class WWWObject : MonoBehaviour
{
public WWW www;
public WWWObject(string url)
{
if(GameVar.wwwCache)
www = WWW.LoadFromCacheOrDownload(url, GameVar.version);
else
www = new WWW(url);
}
public IEnumerator Load()
{
Debug.Log("Start loading : " + www.url);
while(!www.isDone)
{
if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)
LoadScene.progress = www.progress;
yield return 1;
}
if(www.error != null)
Debug.LogError("Loading error : " + www.url + "\n" +www.error);
else
Debug.Log("End loading : " + www.url);
}
public IEnumerator LoadWithTip(string resourcesName)
{
Debug.Log("Start loading : " + www.url);
LoadScene.tipStr = "Downloading resources<" + resourcesName + "> . . .";
while(!www.isDone)
{
if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)
LoadScene.progress= www.progress;
yield return 1;
}
if(www.error != null)
Debug.LogError("Loading error : " + www.url + "\n" +www.error);
else
Debug.Log("End loading : " + www.url);
}
}
調用:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class LoadResources : MonoBehaviour
{
static string url ="http://61.149.211.88/Package/test.unity3d";
public static WWW www = null;
IEnumerator Start()
{
if(!GameVar.resourcesLoaded)
{
GameVar.gameState = GameState.Jumping;
WWWObject obj = new WWWObject(url);
www = obj.www;
yield return StartCoroutine(obj.LoadWithTip("Textures"));
GameVar.resourcesLoaded = true;
GameVar.gameState = GameState.Run;
}
}
}
參考文章:
http://game.ceeger.com/forum/read.php?tid=13148
http://www.zhihu.com/question/23895384
http://blog.csdn.net/tkokof1/article/details/11842673
http://blog.csdn.net/tkokof1/article/details/12834939
http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/
http://dsqiu.iteye.com/blog/2029701
Unity3D中的Coroutine具體解釋