Unity3D中的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,語言本身就支持協程。協程也被叫做輕量級線程。通俗點講就是定義一大堆任務,然後通過一個線程輪着對每個任務都執行一下,協作運行。它的厲害之處在於每運行到一個任務的時候,它都可以從這個任務上一次中斷的地方開始運行。在我們一般的印象中,只有操作系統對線程進行調度的時候纔會幹這樣的事情,進行各種進棧,保存狀態。而協程,總共也只是運行在一個線程中,要是使用線程本身的系統棧,早就暴了。因此在這裏,實現的時候是用內存來模擬棧的操作。具體實現,我想複雜度一定會不小。

我們知道,線程比進程輕量級,因此產生一個線程消耗的資源比進程少,上下文切換也比進程節約。而協程比線程更加輕量級,上下文切換更是迅速。於是在服務器編程方面給人無限想象。儘管目前還沒有出現一款主流的採用協程的web服務器。但是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之間加上return關鍵字。


四、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;

  }

 }

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章