2020-04-16 unity 協(xié)程

目錄

什么是協(xié)程

多線程

協(xié)程

協(xié)程的使用場景

協(xié)程使用示例

Invoke的缺陷

協(xié)程語法

開啟協(xié)程

終止協(xié)程

掛起

協(xié)程的執(zhí)行原理

什么是協(xié)程

在Unity中,協(xié)程(Coroutines)的形式是我最喜歡的功能之一,我都會使用它來控制需要定時的。

協(xié)同程序,在主程序運行的同時,開啟另外一段邏輯處理,來協(xié)同當前程序的執(zhí)行。

可能看了這段文字介紹還是有點模糊,其實可以用多線程來比較。

多線程

多線程,顧名思義,多條同時執(zhí)行的線程。

最初,多線程的誕生是為了解決IO阻塞問題,如今多線程可以解決許多同樣需要異步方法的問題(例如網(wǎng)絡(luò)等)。

所謂異步,通俗點講,就是我走我的線程,你走你的線程。當某個線程阻塞時,另一個線程不會受影響繼續(xù)執(zhí)行。

需要認識到的是,多線程并不是真正意義上的多條線程同時執(zhí)行。

它的實際是將一個時間段分成若干個時間片,每個線程輪流運行一個時間片。

(如圖,將執(zhí)行步驟切分成極小的粒度,然后依次運行)

但是由于時間片粒度非常非常小,幾乎看不出區(qū)別,所以程序執(zhí)行效果跟真正意義上的并行執(zhí)行效果基本一致。

多線程的缺陷

然而多線程有一個壞處,就是可能造成共享數(shù)據(jù)的沖突。

假如有一個變量i = 0, Step1_1的操作是進行++i操作,Step2_1的操作是進行--i操作。

我們預期最終結(jié)果i為0。

但由于操作切分得過小,可能會發(fā)生這樣順序的事:

線程1:訪問i, 將0存到寄存器

線程2:訪問i, 將0存到寄存器

線程1:++i, 得到1

線程2:--i, 得到-1

線程1:將1寫入到i的內(nèi)存

線程2:將-1寫入到i的內(nèi)存

最終i的值為-1

當然多線程的沖突也有解決方案: 互斥鎖....

但是這些多多少少會付出額外的代價,讓程序變得臃腫。

協(xié)程

CPU有多條線程,一條線程可以有多個協(xié)程。

協(xié)程跟多線程類似,也有類似異步的效果(注意不是真正的異步)。

只不過它的切分粒度不是基于系統(tǒng)劃分的時間片,而是基于我們編寫的yield,而且往往粒度更大。

粒度是取決于自己定義什么時候讓協(xié)程掛起:

//下面定義了一個協(xié)程函數(shù),注意必須使用IEnumerator作為返還值才能成為協(xié)程函數(shù)。IEnumerator Test(){for(inti =0; i<1000; ++i){? ? ans += i;yieldreturn0;//掛起,下一幀再來從這個位置繼續(xù)執(zhí)行。}? j+=2;yieldreturn0;//掛起,下一幀再來從這個位置繼續(xù)執(zhí)行。++j;yieldreturn0;//掛起,下一幀再來從這個位置繼續(xù)執(zhí)行。}

如果劃分的粒度過大,協(xié)程所在的線程可能在相應的幀卡頓。

甚至如果讓協(xié)程阻塞(死循環(huán)),那么協(xié)程所在的整個線程也會阻塞。

因此說協(xié)程可以有類似異步的效果,但是不是真正的異步。

協(xié)程的一大好處就是可以避免數(shù)據(jù)訪問沖突的問題:

因為它的粒度相對多線程的大很多,所以往往很少出現(xiàn)沖突現(xiàn)象

在上面多線程的例子里,使用協(xié)程則可以這樣:

Step1_1: 執(zhí)行完++i, 此時i=1

Step2_1: 執(zhí)行完--i, 此時i=0

最終i的值為0

協(xié)程的使用場景

對于保證不會阻塞的并行操作且并行性要求不高的并行操作,可以使用協(xié)程。

更實際來說,協(xié)程最常用于延時執(zhí)行等控制時間軸的操作,例如N秒后調(diào)用指定函數(shù)。

利用每幀執(zhí)行一段協(xié)程的特性,我們可以引入個帶累加計時判斷循環(huán),然后再超過3秒后跳出循環(huán),執(zhí)行Debug.Log()

//3s后執(zhí)行Debug.LogIEnumerator Test(){for(floattimer =0.0f; timer <3.0f ; timer += Time.DeltaTime){yieldreturn0;//掛起,下一幀再來從這個位置繼續(xù)執(zhí)行。}? Debug.Log("啟動協(xié)程3s后");}

但是Unity封裝了個更好用的類:WaitForSeconds

使這種延時的協(xié)程代碼更加簡潔。

//原本寫法for(float timer =0.0f; timer <3.0f; timer += Time.DeltaTime){? ? yieldreturn0;//掛起,下一幀再來從這個位置繼續(xù)執(zhí)行。}//使用WaitForSeconds的寫法yieldreturnnewWaitForSeconds(3.0f);

協(xié)程使用示例

接下來就展示下,協(xié)程使用的示例:

首先編寫好協(xié)程函數(shù)

IEnumerator TestWaitForSeconds(){//3s后執(zhí)行Debug.Log;yieldreturn newWaitForSeconds(3.0f);? ? Debug.Log("啟動協(xié)程3s后");}

然后在某個地方使用StartCoroutine(TestWaitForSeconds())或者StartCoroutine("TestWaitForSeconds")

//啟動協(xié)程:3s后執(zhí)行Debug.logStartCoroutine(TestWaitForSeconds());//啟動后,繼續(xù)往下執(zhí)行...

Invoke的缺陷

另外一提,Unity還有個一樣也是用于延時調(diào)用的函數(shù),叫Invoke

Invoke("test",2.0f); \\延時2秒后執(zhí)行函數(shù)test

但是Invock所要調(diào)用的函數(shù)必須是空類型返還值,還必須得是在當前類里面的方法。

一般來說,用協(xié)程來解決這樣的問題已經(jīng)綽綽有余,而且還有更安全的調(diào)用方法而不是只用string類型作為參數(shù)的方法,因此沒必要使用Invoke。

協(xié)程語法

開啟協(xié)程

StartCoroutine(stringmethodName);

參數(shù)是方法名(字符串類型),此方法可以包含一個參數(shù)。

形參方法可以有返回值

StartCoroutine(IEnumerator method);

參數(shù)是方法(TestMethod()),此方法中可以包含多個參數(shù)。

IEnumrator類型的方法不能含有ref或者out類型的參數(shù),但可以含有被傳遞的引用

形參方法必須有返回值,且返回值類型為IEnumrator,返回值使用(yield retuen +表達式或者值,或者 yield break)語句

終止協(xié)程

StopCoroutine(stringmethodName);//終止指定的協(xié)程

在程序中調(diào)用StopCoroutine()方法只能終止以字符串形式啟動的協(xié)程

StopAllCoroutine();//終止所有協(xié)程

掛起

//程序在下一幀中從當前位置繼續(xù)執(zhí)行yieldreturn0;//程序在下一幀中從當前位置繼續(xù)執(zhí)行yieldreturnnull;//程序等待N秒后從當前位置繼續(xù)執(zhí)行yieldreturn newWaitForSeconds(N);//在所有的渲染以及GUI程序執(zhí)行完成后從當前位置繼續(xù)執(zhí)行yieldnewWaitForEndOfFrame();//所有腳本中的FixedUpdate()函數(shù)都被執(zhí)行后從當前位置繼續(xù)執(zhí)行yieldnewWaitForFixedUpdate();//等待一個網(wǎng)絡(luò)請求完成后從當前位置繼續(xù)執(zhí)行yieldreturnWWW;//等待一個xxx的協(xié)程執(zhí)行完成后從當前位置繼續(xù)執(zhí)行yieldreturnStartCoroutine(xxx);//如果使用yield break語句,將會導致協(xié)程的執(zhí)行條件不被滿足,不會從當前的位置繼續(xù)執(zhí)行程序,而是直接從當前位置跳出函數(shù)體,回到函數(shù)的根部yieldbreak;

協(xié)程的執(zhí)行原理

協(xié)程函數(shù)的返回值時IEnumerator,它是一個迭代器,可以把它當成執(zhí)行一個序列的某個節(jié)點的指針。

它提供了兩個重要的接口,分別是Current(返回當前指向的元素)和MoveNext()(將指針向后移動一個單位,如果移動成功,則返回true)。

yield關(guān)鍵詞用來聲明序列中的下一個值或者是一個無意義的值。

如果使用yield return x(x是指一個具體的對象或者數(shù)值)的話,

那么MoveNext返回為true并且Current被賦值為x,如果使用yield break使得MoveNext()返回為false。

如果MoveNext函數(shù)返回為true意味著協(xié)程的執(zhí)行條件被滿足,則能夠從當前的位置繼續(xù)往下執(zhí)行。否則不能從當前位置繼續(xù)往下執(zhí)行。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容