自:http://www.zhihu.com/question/23895384
說到Coroutine,我們必須提到兩個更遠的東西。在操作系統(os)級別,有進程(process)和線程(thread)兩個(僅從我們常見的講)實際的“東西”(不說概念是因為這兩個家伙的確不僅僅是概念,而是實際存在的,os的代碼管理的資源)。這兩個東西都是用來模擬“並行”的,寫操作系統的程序員通過用一定的策略給不同的進程和線程分配CPU計算資源,來讓用戶“以為”幾個不同的事情在“同時”進行“。在單CPU上,是os代碼強制把一個進程或者線程掛起,換成另外一個來計算,所以,實際上是串行的,只是“概念上的並行”。在現在的多核的cpu上,線程可能是“真正並行的”。
Coroutine,翻譯成”協程“,初始碰到的人馬上就會跟上面兩個概念聯系起來。直接先說區別,Coroutine是編譯器級的,Process和Thread是操作系統級的。Coroutine的實現,通常是對某個語言做相應的提議,然后通過后成編譯器標准,然后編譯器廠商來實現該機制。Process和Thread看起來也在語言層次,但是內生原理卻是操作系統先有這個東西,然后通過一定的API暴露給用戶使用,兩者在這里有不同。Process和Thread是os通過調度算法,保存當前的上下文,然后從上次暫停的地方再次開始計算,重新開始的地方不可預期,每次CPU計算的指令數量和代碼跑過的CPU時間是相關的,跑到os分配的cpu時間到達后就會被os強制掛起。Coroutine是編譯器的魔術,通過插入相關的代碼使得代碼段能夠實現分段式的執行,重新開始的地方是yield關鍵字指定的,一次一定會跑到一個yield對應的地方。
對於Coroutine,下面是一個實現的function,里面的片段被yield關鍵字分成2段:
IEnumerator YieldSomeStuff() { yield "hello"; Console.WriteLine("foo!"); yield "world"; }
推進的代碼(模擬,非實際):
IEnumerator e = YieldSomeStuff();
while(e.MoveNext())
{
Console.WriteLine(e.Current);
}
以此來推進整個代碼片段的分段執行。更詳細的分析如 @鄧凱的文章里提到。這里只要說明的是,對於Coroutine,是編譯器幫助做了很多的事情,來讓代碼不是一次性的跑到底,而不是操作系統強制的掛起。代碼每次跑多少,是可預期的。但是,Process和Thread,在這個層面上完全不同,這兩個東西是操作系統管理的。在unity中,StartCoroutine這個方法是個推進器。StartCoroutine會發起類似上面的while循環。因為是while循環,因此,Coroutine本身其實不是“異步的”。
Coroutine在整個Unity系統的位置,下面一張圖可以說明:注:圖片來自Coroutines++
Unity官方文檔里也寫到"Normal Coroutine在Update之后"的字眼,如下內容第一行:
Normal coroutine updates are run after the Update function returns. A coroutine is a function that can suspend its execution (yield) until the given YieldInstruction finishes. Different uses of Coroutines:
yield; The coroutine will continue after all Update functions have been called on the next frame.
yield WaitForSeconds(2); Continue after a specified time delay, after all Update functions have been called for the frame
yield WaitForFixedUpdate(); Continue after all FixedUpdate has been called on all scripts
yield WWW Continue after a WWW download has completed.
yield StartCoroutine(MyFunc); Chains the coroutine, and will wait for the MyFunc coroutine to complete first.
由上面的圖和文字可以大致猜測,.net虛擬機在每一幀循環中,會依次進入每個編譯器預定義好的入口。對於Coroutine,編譯器需要產生一些代碼,在每次的大循環中,Unity的Update()返回后,保證是yield后的代碼被正確調用,這樣就形成了我們看到的一個function能分段執行的機制。
協程並不是真正的多線程,下面這段代碼在協程中加入死循環,運行就卡住了。
using UnityEngine; using System.Collections; public class WWWtest : MonoBehaviour { public string url = "http://images.earthcam.com/ec_metros/ourcams/fridays.jpg"; private WWW www; public UILabel label; private uint add = 0; void Start() { StartCoroutine(downLoad()); StartCoroutine(Add()); add = 0; } // Update is called once per frame void Update () { label.text = add.ToString(); } IEnumerator downLoad() { yield return null; Debug.Log("start 1"); yield return new WaitForSeconds(5); Debug.Log("1"); www = new WWW(url); Debug.Log("www start"); yield return www; GetComponent<GUITexture>().texture = www.texture; Debug.Log("www done"); } IEnumerator Add() { while (true) ; yield return null; } }