在Unity中,協程(Coroutines)的形式是我最喜歡的功能之一,我都會使用它來控制須要定時的。網絡
協同程序,在主程序運行的同時,開啓另一段邏輯處理,來協同當前程序的執行。
可能看了這段文字介紹仍是有點模糊,其實能夠用多線程來比較。多線程
多線程,顧名思義,多條同時執行的線程。
最初,多線程的誕生是爲了解決IO阻塞問題,現在多線程能夠解決許多一樣須要異步方法的問題(例如網絡等)。
所謂異步,通俗點講,就是我走個人線程,你走你的線程。當某個線程阻塞時,另外一個線程不會受影響繼續執行。異步
須要認識到的是,多線程並非真正意義上的多條線程同時執行。
它的實際是將一個時間段分紅若干個時間片,每一個線程輪流運行一個時間片。函數
(如圖,將執行步驟切分紅極小的粒度,而後依次運行)線程
可是因爲時間片粒度很是很是小,幾乎看不出區別,因此程序執行效果跟真正意義上的並行執行效果基本一致。指針
然而多線程有一個壞處,就是可能形成共享數據的衝突。code
假若有一個變量i = 0, Step1_1的操做是進行++i操做,Step2_1的操做是進行--i操做。
咱們預期最終結果i爲0。協程
但因爲操做切分得太小,可能會發生這樣順序的事:對象
固然多線程的衝突也有解決方案: 互斥鎖....
可是這些多多少少會付出額外的代價,讓程序變得臃腫。
CPU有多條線程,一條線程能夠有多個協程。
協程跟多線程相似,也有相似異步的效果(注意不是真正的異步)。
只不過它的切分粒度不是基於系統劃分的時間片,而是基於咱們編寫的yield,並且每每粒度更大。
粒度是取決於本身定義何時讓協程掛起:
//下面定義了一個協程函數,注意必須使用IEnumerator做爲返還值才能成爲協程函數。 IEnumerator Test() { for(int i = 0; i<1000 ; ++i){ ans += i; yield return 0;//掛起,下一幀再來從這個位置繼續執行。 } j+=2; yield return 0;//掛起,下一幀再來從這個位置繼續執行。 ++j; yield return 0;//掛起,下一幀再來從這個位置繼續執行。 }
若是劃分的粒度過大,協程所在的線程可能在相應的幀卡頓。
甚至若是讓協程阻塞(死循環),那麼協程所在的整個線程也會阻塞。
所以說協程能夠有相似異步的效果,可是不是真正的異步。
協程的一大好處就是能夠避免數據訪問衝突的問題:
由於它的粒度相對多線程的大不少,因此每每不多出現衝突現象
在上面多線程的例子裏,使用協程則能夠這樣:
對於保證不會阻塞的並行操做且並行性要求不高的並行操做,可使用協程。
更實際來講,協程最經常使用於延時執行等控制時間軸的操做,例如N秒後調用指定函數。
利用每幀執行一段協程的特性,咱們能夠引入個帶累加計時判斷循環,而後再超過3秒後跳出循環,執行Debug.Log()
//3s後執行Debug.Log IEnumerator Test() { for(float timer = 0.0f; timer < 3.0f ; timer += Time.DeltaTime){ yield return 0;//掛起,下一幀再來從這個位置繼續執行。 } Debug.Log("啓動協程3s後"); }
可是Unity封裝了個更好用的類:WaitForSeconds
使這種延時的協程代碼更加簡潔。
//本來寫法 for(float timer = 0.0f; timer < 3.0f ; timer += Time.DeltaTime){ yield return 0;//掛起,下一幀再來從這個位置繼續執行。 } //使用WaitForSeconds的寫法 yield return new WaitForSeconds(3.0f);
接下來就展現下,協程使用的示例:
首先編寫好協程函數
IEnumerator TestWaitForSeconds() { //3s後執行Debug.Log; yield return new WaitForSeconds(3.0f); Debug.Log("啓動協程3s後"); }
而後在某個地方使用StartCoroutine(TestWaitForSeconds())或者StartCoroutine("TestWaitForSeconds")
//啓動協程:3s後執行Debug.log StartCoroutine(TestWaitForSeconds()); //啓動後,繼續往下執行 ...
另一提,Unity還有個同樣也是用於延時調用的函數,叫Invoke
Invoke("test",2.0f); \\延時2秒後執行函數test
可是Invock所要調用的函數必須是空類型返還值,還必須得是在當前類裏面的方法。
通常來講,用協程來解決這樣的問題已經綽綽有餘,並且還有更安全的調用方法而不是隻用string類型做爲參數的方法,所以不必使用Invoke。
StartCoroutine(string methodName);
StartCoroutine(IEnumerator method);
StopCoroutine(string methodName);//終止指定的協程
StopAllCoroutine();//終止全部協程
//程序在下一幀中從當前位置繼續執行 yield return 0; //程序在下一幀中從當前位置繼續執行 yield return null; //程序等待N秒後從當前位置繼續執行 yield return new WaitForSeconds(N); //在全部的渲染以及GUI程序執行完成後從當前位置繼續執行 yield new WaitForEndOfFrame(); //全部腳本中的FixedUpdate()函數都被執行後從當前位置繼續執行 yield new WaitForFixedUpdate(); //等待一個網絡請求完成後從當前位置繼續執行 yield return WWW; //等待一個xxx的協程執行完成後從當前位置繼續執行 yield return StartCoroutine(xxx); //若是使用yield break語句,將會致使協程的執行條件不被知足,不會從當前的位置繼續執行程序,而是直接從當前位置跳出函數體,回到函數的根部 yield break;
協程函數的返回值時IEnumerator,它是一個迭代器,能夠把它當成執行一個序列的某個節點的指針。
它提供了兩個重要的接口,分別是Current(返回當前指向的元素)和MoveNext()(將指針向後移動一個單位,若是移動成功,則返回true)。
yield關鍵詞用來聲明序列中的下一個值或者是一個無心義的值。
若是使用yield return x(x是指一個具體的對象或者數值)的話, 那麼MoveNext返回爲true而且Current被賦值爲x,若是使用yield break使得MoveNext()返回爲false。 若是MoveNext函數返回爲true意味着協程的執行條件被知足,則可以從當前的位置繼續往下執行。不然不能從當前位置繼續往下執行。