轉載請标明出處:http://www.cnblogs.com/zblade/
一、序言
在unity的遊戲開發中,對于異步操作,有一個避免不了的操作: 協程,以前一直了解的懵懵懂懂,最近認真充電了一下,通過前輩的文章大體了解了一下,在這兒抛磚引玉寫一些個人了解。當然首先給出幾篇寫的非常精彩優秀的文章,最好認真拜讀一下:
王迅:Coroutine從入門到勸退zhuanlan.zhihu.com
Unity3d中協程的原理,你要的yield return new xxx的真正了解之道blog.csdn.net
Unity協程(Coroutine)原理深入剖析dsqiu.iteye.com
好了,接下來就從一個小白的視角開始了解協程。
二、常見使用協程的示例
經常,我們會利用monobehaviour的startcoroutine來開啟一個協程,這是我們在使用unity中最常見的直覺了解。在這個協程中執行一些異步操作,比如下載下傳檔案,加載檔案等,在完成這些操作後,執行我們的回調。 舉例說明:
這個例子中,用到了幾個關鍵詞: IEnumerator/yield return xxx/ yield break/StartCoroutine, 那麼我們從這幾個關鍵詞入手,去了解這樣的一個下載下傳操作具體實作。
1、關鍵詞 IEnumerator
這個關鍵詞不是在Unity中特有,unity也是來自c#,是以找一個c#的例子來了解比較合适。首先看看IEnumerator的定義:
從定義可以了解,一個疊代器,三個基本的操作:Current/MoveNext/Reset, 這兒簡單說一下其操作的過程。在常見的集合中,我們使用foreach這樣的枚舉操作的時候,最開始,枚舉數被定為在集合的第一個元素前面,Reset操作就是将枚舉數傳回到此位置。
疊代器在執行疊代的時候,首先會執行一個 MoveNext, 如果傳回true,說明下一個位置有對象,然後此時将Current設定為下一個對象,這時候的Current就指向了下一個對象。當然c#是如何将這個IEnumrator編譯成一個對象示例來執行,下面會講解到。
2、關鍵詞 Yield
c#中的yield關鍵詞,後面有兩種基本的表達式:
yield break就是跳出協程的操作,一般用在報錯或者需要退出協程的地方。
yield return是用的比較多的表達式,具體的expresion可以以下幾個常見的示例:
好了,有了對幾個關鍵詞的了解,接下來我們看看c#編譯器是如何把我們寫的協程調用編譯生成的。
三、c#對協程調用的編譯結果
這兒沒有把上面的例子編譯生成,就借用一下前面文章中的例子 :b
其編譯器生成的c++結果:
代碼比較直覺,相關的注釋也寫了一點,是以我們在執行開啟一個協程的時候,其本質就是傳回一個疊代器的執行個體,然後在主線程中,每次update的時候,都會更新這個執行個體,判斷其是否執行MoveNext的操作,如果可以執行(比如檔案下載下傳完成),則執行一次MoveNext,将下一個對象指派給Current(MoveNext需要傳回為true, 如果為false表明疊代執行完成了)。
通過這兒,可以得到一個結論,協程并不是異步的,其本質還是在Unity的主線程中執行,每次update的時候都會觸發是否執行MoveNext。
四、協程的衍生使用
既然IEnumerator可以這樣用,那我們其實可以隻使用MoveNext和Current,就可以寫一個簡易的測試協程的例子,Ok,來寫一個簡易的例子,來自leader的代碼,偷懶就複用了 :D
說一下思路: 在開始的時候,建構一個IEnuerator執行個體塞傳入連結表中,然後再後續的每幀update的時候,取出這個執行個體,執行一次MoveNext,一直到都執行完後,移除這個執行個體,這樣就不用顯示的調用StartCoroutine,也可以類似的觸發執行MoveNext :D
看運作結果:

可行。OK,關于unity的協程就寫到這兒了,接下來将一下xlua中對于協程的實作。
五、Lua中的協程
Lua中的協程和unity協程的差別,最大的就是其不是搶占式的執行,也就是說不會被主動執行類似MoveNext這樣的操作,而是需要我們去主動激發執行,就像上一個例子一樣,自己去tick這樣的操作。
Lua中協程關鍵的三個API:
coroutine.create()/wrap: 建構一個協程, wrap建構結果為函數,create為thread類型對象
coroutine.resume(): 執行一次類似MoveNext的操作
coroutine.yield(): 将協程挂起
比較簡易,可以寫也給例子測試一下:
我們來看看xlua開源出來的util中對協程的使用示例又是怎麼結合lua的協程,在lua端建構也給協程,讓c#端也可以擷取這個執行個體,進而添加到unity端的主線程中去觸發update。
看一下調用的API:
start操作,本質就是将function包一層,調用util.csgenerator,進一步看看util中對cs_generator的實作:
代碼很短,不過思路很清晰,首先建構一個table, 其中的key對應一個function,然後修改去元表的_index方法,其中包含了MoveNext函數的實作,也包含了Reset函數的實作,不過這兒的Reset和IEnumerator的不一樣,這兒是調用coroutine.wrap來生成一個協程。這樣c#端擷取到這個generator的handleID後,後面每幀update回來都會執行一次MoveNext,如果都執行完了,這時候會return move_end,表明協程都執行完了,傳回false給c#端清空該協程的handleID.