Unity C#笔记 协程详解(转)
目錄
- 什么是協(xié)程
- 多線程
- 協(xié)程
- 協(xié)程的使用場(chǎng)景
- 協(xié)程使用示例
- Invoke的缺陷
- 協(xié)程語(yǔ)法
- 開啟協(xié)程
- 終止協(xié)程
- 掛起
- 協(xié)程的執(zhí)行原理
什么是協(xié)程
在Unity中,協(xié)程(Coroutines)的形式是我最喜歡的功能之一,我都會(huì)使用它來控制需要定時(shí)的。
協(xié)同程序,在主程序運(yùn)行的同時(shí),開啟另外一段邏輯處理,來協(xié)同當(dāng)前程序的執(zhí)行。
可能看了這段文字介紹還是有點(diǎn)模糊,其實(shí)可以用多線程來比較。
多線程
多線程,顧名思義,多條同時(shí)執(zhí)行的線程。
最初,多線程的誕生是為了解決IO阻塞問題,如今多線程可以解決許多同樣需要異步方法的問題(例如網(wǎng)絡(luò)等)。
所謂異步,通俗點(diǎn)講,就是我走我的線程,你走你的線程。當(dāng)某個(gè)線程阻塞時(shí),另一個(gè)線程不會(huì)受影響繼續(xù)執(zhí)行。
需要認(rèn)識(shí)到的是,多線程并不是真正意義上的多條線程同時(shí)執(zhí)行。
它的實(shí)際是將一個(gè)時(shí)間段分成若干個(gè)時(shí)間片,每個(gè)線程輪流運(yùn)行一個(gè)時(shí)間片。
(如圖,將執(zhí)行步驟切分成極小的粒度,然后依次運(yùn)行)
但是由于時(shí)間片粒度非常非常小,幾乎看不出區(qū)別,所以程序執(zhí)行效果跟真正意義上的并行執(zhí)行效果基本一致。
多線程的缺陷
然而多線程有一個(gè)壞處,就是可能造成共享數(shù)據(jù)的沖突。
假如有一個(gè)變量i = 0, Step1_1的操作是進(jìn)行++i操作,Step2_1的操作是進(jìn)行--i操作。
我們預(yù)期最終結(jié)果i為0。
但由于操作切分得過小,可能會(huì)發(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
當(dāng)然多線程的沖突也有解決方案: 互斥鎖....
但是這些多多少少會(huì)付出額外的代價(jià),讓程序變得臃腫。
協(xié)程
CPU有多條線程,一條線程可以有多個(gè)協(xié)程。
協(xié)程跟多線程類似,也有類似異步的效果(注意不是真正的異步)。
只不過它的切分粒度不是基于系統(tǒng)劃分的時(shí)間片,而是基于我們編寫的yield,而且往往粒度更大。
粒度是取決于自己定義什么時(shí)候讓協(xié)程掛起:
//下面定義了一個(gè)協(xié)程函數(shù),注意必須使用IEnumerator作為返還值才能成為協(xié)程函數(shù)。 IEnumerator Test() { for(int i = 0; i<1000 ; ++i){ ans += i; yield return 0;//掛起,下一幀再來從這個(gè)位置繼續(xù)執(zhí)行。 } j+=2; yield return 0;//掛起,下一幀再來從這個(gè)位置繼續(xù)執(zhí)行。 ++j; yield return 0;//掛起,下一幀再來從這個(gè)位置繼續(xù)執(zhí)行。 }如果劃分的粒度過大,協(xié)程所在的線程可能在相應(yīng)的幀卡頓。
甚至如果讓協(xié)程阻塞(死循環(huán)),那么協(xié)程所在的整個(gè)線程也會(huì)阻塞。
因此說協(xié)程可以有類似異步的效果,但是不是真正的異步。
協(xié)程的一大好處就是可以避免數(shù)據(jù)訪問沖突的問題:
因?yàn)樗牧6认鄬?duì)多線程的大很多,所以往往很少出現(xiàn)沖突現(xiàn)象
在上面多線程的例子里,使用協(xié)程則可以這樣:
- Step1_1: 執(zhí)行完++i, 此時(shí)i=1
- Step2_1: 執(zhí)行完--i, 此時(shí)i=0
- 最終i的值為0
協(xié)程的使用場(chǎng)景
對(duì)于保證不會(huì)阻塞的并行操作且并行性要求不高的并行操作,可以使用協(xié)程。
更實(shí)際來說,協(xié)程最常用于延時(shí)執(zhí)行等控制時(shí)間軸的操作,例如N秒后調(diào)用指定函數(shù)。
利用每幀執(zhí)行一段協(xié)程的特性,我們可以引入個(gè)帶累加計(jì)時(shí)判斷循環(huán),然后再超過3秒后跳出循環(huán),執(zhí)行Debug.Log()
//3s后執(zhí)行Debug.Log IEnumerator Test() { for(float timer = 0.0f; timer < 3.0f ; timer += Time.DeltaTime){ yield return 0;//掛起,下一幀再來從這個(gè)位置繼續(xù)執(zhí)行。 } Debug.Log("啟動(dòng)協(xié)程3s后"); }但是Unity封裝了個(gè)更好用的類:WaitForSeconds
使這種延時(shí)的協(xié)程代碼更加簡(jiǎn)潔。
協(xié)程使用示例
接下來就展示下,協(xié)程使用的示例:
首先編寫好協(xié)程函數(shù)
然后在某個(gè)地方使用StartCoroutine(TestWaitForSeconds())或者StartCoroutine("TestWaitForSeconds")
//啟動(dòng)協(xié)程:3s后執(zhí)行Debug.logStartCoroutine(TestWaitForSeconds());//啟動(dòng)后,繼續(xù)往下執(zhí)行...Invoke的缺陷
另外一提,Unity還有個(gè)一樣也是用于延時(shí)調(diào)用的函數(shù),叫Invoke
Invoke("test",2.0f); \\延時(shí)2秒后執(zhí)行函數(shù)test但是Invock所要調(diào)用的函數(shù)必須是空類型返還值,還必須得是在當(dāng)前類里面的方法。
一般來說,用協(xié)程來解決這樣的問題已經(jīng)綽綽有余,而且還有更安全的調(diào)用方法而不是只用string類型作為參數(shù)的方法,因此沒必要使用Invoke。
協(xié)程語(yǔ)法
開啟協(xié)程
StartCoroutine(string methodName);- 參數(shù)是方法名(字符串類型),此方法可以包含一個(gè)參數(shù)。
- 形參方法可以有返回值
- 參數(shù)是方法(TestMethod()),此方法中可以包含多個(gè)參數(shù)。
- IEnumrator類型的方法不能含有ref或者out類型的參數(shù),但可以含有被傳遞的引用
- 形參方法必須有返回值,且返回值類型為IEnumrator,返回值使用(yield retuen +表達(dá)式或者值,或者 yield break)語(yǔ)句
終止協(xié)程
StopCoroutine(string methodName);//終止指定的協(xié)程- 在程序中調(diào)用StopCoroutine()方法只能終止以字符串形式啟動(dòng)的協(xié)程
掛起
//程序在下一幀中從當(dāng)前位置繼續(xù)執(zhí)行 yield return 0; //程序在下一幀中從當(dāng)前位置繼續(xù)執(zhí)行 yield return null; //程序等待N秒后從當(dāng)前位置繼續(xù)執(zhí)行 yield return new WaitForSeconds(N); //在所有的渲染以及GUI程序執(zhí)行完成后從當(dāng)前位置繼續(xù)執(zhí)行 yield new WaitForEndOfFrame(); //所有腳本中的FixedUpdate()函數(shù)都被執(zhí)行后從當(dāng)前位置繼續(xù)執(zhí)行 yield new WaitForFixedUpdate(); //等待一個(gè)網(wǎng)絡(luò)請(qǐng)求完成后從當(dāng)前位置繼續(xù)執(zhí)行 yield return WWW; //等待一個(gè)xxx的協(xié)程執(zhí)行完成后從當(dāng)前位置繼續(xù)執(zhí)行 yield return StartCoroutine(xxx); //如果使用yield break語(yǔ)句,將會(huì)導(dǎo)致協(xié)程的執(zhí)行條件不被滿足,不會(huì)從當(dāng)前的位置繼續(xù)執(zhí)行程序,而是直接從當(dāng)前位置跳出函數(shù)體,回到函數(shù)的根部 yield break;協(xié)程的執(zhí)行原理
協(xié)程函數(shù)的返回值時(shí)IEnumerator,它是一個(gè)迭代器,可以把它當(dāng)成執(zhí)行一個(gè)序列的某個(gè)節(jié)點(diǎn)的指針。
它提供了兩個(gè)重要的接口,分別是Current(返回當(dāng)前指向的元素)和MoveNext()(將指針向后移動(dòng)一個(gè)單位,如果移動(dòng)成功,則返回true)。
yield關(guān)鍵詞用來聲明序列中的下一個(gè)值或者是一個(gè)無意義的值。
如果使用yield return x(x是指一個(gè)具體的對(duì)象或者數(shù)值)的話,
那么MoveNext返回為true并且Current被賦值為x,如果使用yield break使得MoveNext()返回為false。
如果MoveNext函數(shù)返回為true意味著協(xié)程的執(zhí)行條件被滿足,則能夠從當(dāng)前的位置繼續(xù)往下執(zhí)行。否則不能從當(dāng)前位置繼續(xù)往下執(zhí)行。
作者:KillerAery 出處:http://www.cnblogs.com/KillerAery/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
轉(zhuǎn)載于:https://www.cnblogs.com/Roz-001/p/11205700.html
總結(jié)
以上是生活随笔為你收集整理的Unity C#笔记 协程详解(转)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件测试常用的黑盒测试方法有哪些,简述什
- 下一篇: C#/winform 旅游管理信息系统