這裡himi強調一點:unity裡面的協程并不是線程,協程是在unity主線程中運作的,每一幀中處理一次,而并不與主線程并行。這就意味着在協程之間并不存在着所謂線程間的同步和互斥問題,不會出現死鎖。一般來說,通路同一個值也都是很安全的,用協程可以處理絕大多數的小問題,而且不用考慮複雜的線程間同步,還是很友善的。
要說協程的不足就是不能運用處理器的多核來提高處理性能,畢竟這個在運作時事實上是在一個線程中執行的。
【以下均為轉載内容】
在unity中,延時執行一段代碼或者一個方法或者幾個方法的情況非常普遍。
一般會用到invoke和invokerepeating方法。顧名思義,第一個是執行一次,第二個是重複執行。
看下定義:
void invoke(string methodname, float time);
第一個參數是方法名(注意是字元串形式),并不是更友善的委托。第二個是延時多少秒。隻執行一次。
void invokerepeating(string methodname, float time, float repeatrate);
invokerepeating第二個參數是延時多少秒後開始,第三個參數是每次執行間隔的秒數。
你有沒有發現這兩個方法有個弊端,就是必須輸入方法名!也就是我說,如果我想延時執行某段代碼,必須把代碼放在某個方法裡,然後使用這invoke或者invokerepeating方法來執行。
這樣對于上下文變量、屬性的引用就會尤為不便,而且不能傳參數!!!尼瑪,要他還有何用?
我猜你一定用過這樣的方法。沒錯,“協同”,聽起來還挺高大上的名字啊。
用startcoroutine來執行一個以ienumerator為傳回值的方法,通常用于異步下載下傳啊,等比較耗時又不能讓遊戲卡死的情況。
還有一個好的類waitforseconds,對,它就一個構造函數,用來延時的(延時………………比萬艾可好用?比希愛力好用?)。
好了不廢話了,以下是我自用的延時方法,放在一個類裡以靜态方法存在。可以在任何時候任何地方延時指定秒數的代碼。
using unityengine;
using system.collections;
using system;
public class delaytoinvoke : monobehaviour
{
public static ienumerator delaytoinvokedo(action action, float delayseconds)
yield return new waitforseconds(delayseconds);
action();
}
如何使用呢?
比如我點選ngui的一個button,則
void onclick()
startcoroutine(delaytoinvoke.delaytoinvokedo(() =>
application.loadlevel(“option”);
}, 0.1f));
尊重他人的勞動,支援原創,轉載請注明出處:http.dsqiu.iteye.com
記得去年6月份剛開始實習的時候,當時要我寫網絡層的結構,用到了協程,當時有點懵,完全不知道unity協程的執行機制是怎麼樣的,隻是知道函數的傳回值是ienumerator類型,函數中使用yield return ,就可以通過startcoroutine調用了。後來也是一直稀裡糊塗地用,上網google些基本都是例子,很少能幫助深入了解unity協程的原理的。
本文隻是從unity的角度去分析了解協程的内部運作原理,而不是從c#底層的文法實作來介紹(後續有需要再進行介紹),一共分為三部分:
線程(thread)和協程(coroutine)
unity中協程的執行原理
ienumerator & coroutine
d.s.qiu覺得使用協程的作用一共有兩點:1)延時(等待)一段時間執行代碼;2)等某個操作完成之後再執行後面的代碼。總結起來就是一句話:控制代碼在特定的時機執行。
很多初學者,都會下意識地覺得協程是異步執行的,都會覺得協程是c# 線程的替代品,是unity不使用線程的解決方案。
是以首先,請你牢記:協程不是線程,也不是異步執行的。協程和 monobehaviour 的 update函數一樣也是在mainthread中執行的。使用協程你不用考慮同步和鎖的問題。
unitygems.com給出了協程的定義:
a coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.
即協程是一個分部執行,遇到條件(yield return 語句)會挂起,直到條件滿足才會被喚醒繼續執行後面的代碼。
unity在每一幀(frame)都會去處理對象上的協程。unity主要是在update後去處理協程(檢查協程的條件是否滿足),但也有寫特例:
從上圖的剖析就明白,協程跟update()其實一樣的,都是unity每幀對會去處理的函數(如果有的話)。如果monobehaviour 是處于激活(active)狀态的而且yield的條件滿足,就會協程方法的後面代碼。還可以發現:如果在一個對象的前期調用協程,協程會立即運作到第一個 yield return 語句處,如果是 yield return null ,就會在同一幀再次被喚醒。如果沒有考慮這個細節就會出現一些奇怪的問題『1』。
『1』注 圖和結論都是從unitygems.com 上得來的,經過下面的驗證發現與實際不符,d.s.qiu用的是unity 4.3.4f1 進行測試的。經過測試驗證,協程至少是每幀的lateupdate()後去運作。
下面使用 yield return new waitforseconds(1f); 在start,update 和 lateupdate 中分别進行測試:
using unityengine;
using system.collections;
public class testcoroutine : monobehaviour {
private bool isstartcall = false; //makesure update() and lateupdate() log only once
private bool isupdatecall = false;
private bool islateupdatecall = false;
// use this for initialization
void start () {
if (!isstartcall)
{
debug.log(“start call begin”);
startcoroutine(startcoutine());
debug.log(“start call end”);
isstartcall = true;
}
}
ienumerator startcoutine()
{
debug.log(“this is start coroutine call before”);
yield return new waitforseconds(1f);
debug.log(“this is start coroutine call after”);
// update is called once per frame
void update () {
if (!isupdatecall)
debug.log(“update call begin”);
startcoroutine(updatecoutine());
debug.log(“update call end”);
isupdatecall = true;
ienumerator updatecoutine()
debug.log(“this is update coroutine call before”);
debug.log(“this is update coroutine call after”);
void lateupdate()
if (!islateupdatecall)
debug.log(“lateupdate call begin”);
startcoroutine(latecoutine());
debug.log(“lateupdate call end”);
islateupdatecall = true;
ienumerator latecoutine()
debug.log(“this is late coroutine call before”);
debug.log(“this is late coroutine call after”);
得到日志輸入結果如下:
然後将yield return new waitforseconds(1f);改為 yield return null; 發現日志輸入結果和上面是一樣的,沒有出現上面說的情況:
yield return null;
『今天意外發現monobehaviour的函數執行順序圖,發現協程的運作确實是在lateupdate之後,下面附上:』
增補于:03/12/2014 22:14
經過驗證,『2』的結論也是錯誤的,正确的結論是,monobehaviour.enabled = false 協程會照常運作,但 gameobject.setactive(false) 後協程卻全部停止,即使在inspector把 gameobject 激活還是沒有繼續執行:
this.enabled = false;
//this.gameobject.setactive(false);
debug.log(“this is update coroutine call second”);
先在update中調用 this.enabled = false; 得到的結果:
然後把 this.enabled = false; 注釋掉,換成 this.gameobject.setactive(false); 得到的結果如下:
整理得到:通過設定monobehaviour腳本的enabled對協程是沒有影響的,但如果 gameobject.setactive(false) 則已經啟動的協程則完全停止了,即使在inspector把gameobject 激活還是沒有繼續執行。也就說協程雖然是在monobehvaviour啟動的(startcoroutine)但是協程函數的地位完全是跟monobehaviour是一個層次的,不受monobehaviour的狀态影響,但跟monobehaviour腳本一樣受gameobject 控制,也應該是和monobehaviour腳本一樣每幀“輪詢” yield 的條件是否滿足。
yield 後面可以有的表達式:
a) null – the coroutine executes the next time that it is eligible
b) waitforendofframe – the coroutine executes on the frame, after all of the rendering and gui is complete
c) waitforfixedupdate – causes this coroutine to execute at the next physics step, after all physics is calculated
d) waitforseconds – causes the coroutine not to execute for a given game time period
e) www – waits for a web request to complete (resumes as if waitforseconds or null)
f) another coroutine – in which case the new coroutine will run to completion before the yielder is resumed
值得注意的是 waitforseconds()受time.timescale影響,當time.timescale = 0f 時,yield return new waitforsecond(x) 将不會滿足。
ienumerator & coroutine
unity在每幀做的工作就是:調用 協程(疊代器)movenext() 方法,如果傳回 true ,就從目前位置繼續往下執行。
hijack
這裡在介紹一個協程的交叉調用類 hijack(參見附件):
using system;
using system.collections.generic;
using system.linq;
[requirecomponent(typeof(guitext))]
public class hijack : monobehaviour {
//this will hold the counting up coroutine
ienumerator _countup;
//this will hold the counting down coroutine
ienumerator _countdown;
//this is the coroutine we are currently
//hijacking
ienumerator _current;
//a value that will be updated by the coroutine
//that is currently running
int value = 0;
void start()
//create our count up coroutine
_countup = countup();
//create our count down coroutine
_countdown = countdown();
//start our own coroutine for the hijack
startcoroutine(dohijack());
void update()
//show the current value on the screen
guitext.text = value.tostring();
void ongui()
//switch between the different functions
if(guilayout.button(“switch functions”))
if(_current == _countup)
_current = _countdown;
else
_current = _countup;
ienumerator dohijack()
while(true)
//check if we have a current coroutine and movenext on it if we do
if(_current != null && _current.movenext())
{
//return whatever the coroutine yielded, so we will yield the
//same thing
yield return _current.current;
}
//otherwise wait for the next frame
yield return null;
ienumerator countup()
//we have a local increment so the routines
//get independently faster depending on how
//long they have been active
float increment = 0;
//exit if the q button is pressed
if(input.getkey(keycode.q))
break;
increment+=time.deltatime;
value += mathf.roundtoint(increment);
yield return null;
ienumerator countdown()
float increment = 0f;
value -= mathf.roundtoint(increment);
//this coroutine returns a yield instruction
yield return new waitforseconds(0.1f);
上面的代碼實作是兩個協程交替調用,對有這種需求來說實在太精妙了。
小結:
今天仔細看了下unitygems.com 有關coroutine的兩篇文章,雖然第一篇(參考①)現在驗證的結果有很多錯誤,但對于了解協程還是不錯的,尤其是當我發現hijack這個腳本時,就迫不及待分享給大家。
本來沒覺得會有unitygems.com上的文章會有錯誤的,無意測試了發現還是有很大的出入,當然這也不是說原來作者沒有經過驗證就妄加揣測,d.s.qiu覺得很有可能是unity内部的實作機制改變了,這種東西完全可以改動,unity雖然開發了很多年了,但是其實在實際開發中還是有很多坑,越發覺得unity的無力,雖說容易上手,但是填坑的功夫也是必不可少的。
看來很多結論還是要通過自己的驗證才行,貿然複制粘貼很難出真知,切記!