目錄
什么是協(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í)行。