Unity Coroutine詳解(一)

Unity 中協程是個很是強大的功能,其做用主要是用於遊戲中的延時調用或者執行一連串的有時間間隔的事件流程,例如劇情對話等。簡單總結了幾點協程相關的知識點,旨在加深記憶,同時爲初學者解惑。程序員

 

一、協程、進程與線程面試

這是個面試中常常會問到的問題:協程、進程與線程的區別在哪?網絡

  說到協程,咱們首先回顧如下線程與進程這兩個概念。在操做系統(os)級別,有進程(process)和線程(thread)兩個咱們看不到但又實際存在的「東西」,這兩個東西都是用來模擬「並行」的,寫操做系統的程序員經過用必定的策略給不一樣的進程和線程分配CPU計算資源,來讓用戶「覺得」幾個不一樣的事情在「同時」進行「。在單CPU上,是os代碼強制把一個進程或者線程掛起,換成另一個來計算,因此,其實是串行的,只是「概念上的並行」。在如今的多核的cpu上,線程多是「真正並行的」。

多線程

進程擁有本身獨立的堆和棧,既不共享堆,亦不共享棧,進程由操做系統調度。函數

線程擁有本身獨立的棧和共享的堆,共享堆,不共享棧,線程亦由操做系統調度(標準線程是的)。工具

協程和線程同樣共享堆,不共享棧,協程由程序員在協程的代碼裏顯示調度。性能

一個應用程序通常對應一個進程,一個進程通常有一個主線程,還有若干個輔助線程,線程之間是平行運行的,在線程裏面能夠開啓協程,讓程序在特定的時間內運行。spa

 

協程和線程的區別是:協程避免了無心義的調度,由此能夠提升性能,但也所以,程序員必須本身承擔調度的責任,同時,協程也失去了標準線程使用多CPU的能力。操作系統

打個比方吧,假設有一個操做系統,是單核的,系統上沒有其餘的程序須要運行,有兩個線程 A 和 B ,A 和 B 在單獨運行時都須要 10 秒來完成本身的任務,並且任務都是運算操做,A B 之間也沒有競爭和共享數據的問題。如今 A B 兩個線程並行,操做系統會不停的在 A B 兩個線程之間切換,達到一種僞並行的效果,假設切換的頻率是每秒一次,切換的成本是 0.1 秒(主要是棧切換),總共須要 20 + 19 * 0.1 = 21.9 秒。若是使用協程的方式,能夠先運行協程 A ,A 結束的時候讓位給協程 B ,只發生一次切換,總時間是 20 + 1 * 0.1 = 20.1 秒。若是系統是雙核的,並且線程是標準線程,那麼 A B 兩個線程就能夠真並行,總時間只須要 10 秒,而協程的方案仍然須要 20.1 秒。線程

 

  其實就根原本說,協程除了名字以外,和線程是沒有太大聯繫的。Unity中的特殊在於全部的腳本和代碼都是在一個主線程裏運行的,協程也不例外。協程與線程的類似點只在於,協程看起來也能夠與其餘函數並行執行。 但本質上來講,線程是經過能夠開啓多個子線程同時執行程序,而達到並行。而協程則是經過每幀檢測的方式,在本身與其餘函數之間切換。這種「來回跑」的方式,與Unity中一慣有明確執行順序的風格(腳本生命週期)看起來不太統一。但這正是它的強大之處,使得咱們在使用協程的時候沒必要考慮lock等諸多線程中的問題。

 二、協程執行原理

unity中協程執行過程當中,經過yield return XXX,將程序掛起,去執行接下來的內容,注意協程不是線程,在爲遇到yield return XXX語句以前,協程額方法和通常的方法是相同的,也就是程序在執行到yield return XXX語句以後,接着纔會執行的是 StartCoroutine()方法以後的程序,走的仍是單線程模式,僅僅是將yield return XXX語句以後的內容暫時掛起,等到特定的時間才執行。
那麼掛起的程序何時才執行,這就要看monoBehavior的生命週期了。

也就是協同程序主要是update()方法以後,lateUpdate()方法以前調用的,接下來咱們經過一個小例子去理解一下。

using UnityEngine;
using System.Collections;
using System.Threading;
public class test : MonoBehaviour
{

    void Start()
    {
        StartCoroutine(tt());//開啓協程
        for (int i = 0; i < 200; i++)   //循環A
        {
            Debug.Log("*************************" + i);
            Thread.Sleep(10);
        }
    }

    IEnumerator tt()
    {
        for (int i = 0; i < 100; i++) //循環B
        {
            Debug.Log("-------------------" + i);
        }

        yield return new WaitForSeconds(1); //協程1

        for (int i = 0; i < 100; i++) //循環C
        {
            Debug.Log(">>>>>>>>>>>>>>>>>>>>" + i);
            yield return null; //協程1
        }
    }

    // 更新數據
    void Update()
    {
        Debug.Log("Update");
    }

    //晚於更新
    void LateUpdate()
    {
        Debug.Log("------LateUpdate");
    }

}

程序的運行結果爲:

先執行循環B,而後執行循環A,而後執行update()和lateUpdate()的方法,等待1S以後,在updat()和lateupda()之間執行循環C的輸出。

 

三、yield return 的不一樣返回類型

  使用yield return的時候你會發現它能夠返回的類型一長串,對於初學者我以爲就分爲帶 new和不帶new的就好了。

  先說不帶new的。一般能夠yield return的有 null,數字 ,字符串,布爾值甚至表達式,函數,嵌套協程等。

  以在Start()中開啓當前協程爲例,若是是不帶new的返回類型,執行時間都是同樣的。即在第一時間執行協程中的代碼 到第一個yield return當行爲止,而後在下一幀的Update以後,LateUpdate以前執行yield return後面的代碼。

  另外須要注意的是,yield return後面能夠是一個函數調用,賦值表達式,嵌套的其它協程等。以賦值的表達式num=10爲例;它會在當行yield return執行的時候就執行,函數調用和其它協程也是同樣。也就是說,此時yield return的函數調用就至關於直接調用了這個函數,而且是當時就執行的。 而其它return 類型 如null,字符串,數字等通常只用做延遲一幀來用,其它做用,待我後期再研究下。

  下面說帶new的,也是一般咱們重點使用的協程功能。

   這裏列舉幾個:

  (1)new  WaitUntil(Func<bool>)  參數是一個布爾返回類型的委託,做用是,知道這個返回的布爾值爲true時,協程纔會繼續執行當行yield return 後面的代碼。       

  (2) new WaitForSeconds(float)參數是float類型的數字,表示秒,也是協程最經常使用的功能之一。 做用是,在N秒後纔會繼續執行當行yield return 後面的代碼。 

因爲yield return能夠在一個協程中任意位置寫多個,配合這個能夠實現不少時間細化可視化的功能。

  (3)new WaitForEndOfFrame()做用是,在結束當前幀 攝像機和GUI被渲染以及其它函數完成後纔會繼續執行當行yield return 後面的代碼。 這個我只驗證了在LateUpdate執行完以後執行,具體在整個腳本週期中哪一個函數執行完以後開始執行還未詳細驗證。

  (4)new  WaitForFixedUpdate()  做用是,直到當行代碼以後第一個FixedUpdate執行以後纔會繼續執行當行yield return 後面的代碼。也就是說,若是是在start裏面開啓協程的話,第一次執行FixedUpdate以後就會繼續執行return後面的代碼。

  後面還有許多類型的 返回,沒有一一驗證,不過做用應該大同小異,即在執行第一個該類型動做以後纔會繼續執行當行yield return 後面的代碼。

  值得一提的是,協程的延遲調用和非阻塞式掛起是用於網絡請求等高級結構很好的工具,很是值得花一些時間去仔細研究。

四、開始協程

  經過MonoBehaviour提供的StartCoroutine方法來實現啓動協同程序。

  一、StartCoroutine(IEnumerator routine);

  優勢:靈活,性能開銷小。

  缺點:沒法單獨的中止這個協程,若是須要中止這個協程只能等待協同程序運行完畢或則使用StopAllCoroutine();方法。

  二、StartCoroutine (methodName:string, value : object = null);

  優勢:能夠直接經過傳入協同程序的方法名來中止這個協程:StopCoroutine(string methodName);

  缺點:性能的開銷較大,只能傳遞一個參數。

 

五、中止協程

  協程內中止能夠用yield return break;

  協程外:

  一、StopCoroutine(string methodName);

  二、StopAllCoroutine();

  三、設置gameobject的active爲false時能夠終止協同程序,可是再次設置爲true後協程不會再啓動。設置當前協程所在腳本enable爲false也並不能中止當前協程的執行。

相關文章
相關標籤/搜索