對 Unity 協程的調研

1. 什麼是協程 #

A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done. → 協程是一個部分執行, 遇到條件 (yield return) 會掛起, 直到條件知足纔會被喚醒繼續執行後面代碼的一種函數.html

2. 爲何要有協程 #

協程的做用一共有兩點 :編程

1. 延時等待一段時間執行代碼
2. 等某個操做完成以後再執行後面的代碼

總而言之, 協程控制了代碼在特定的時機執行, 是對單線程控制權的交替.數據結構

3. 協程的原理 #

Coroutines 不是多線程, 不是異步技術, 協程都在 MainThread 中執行, 並且每一個時刻只有一個 Coroutine 在執行. Coroutine 是一個 function, 能夠部分執行, 當條件知足時, 將來會被再次執行直到整個函數執行完畢.多線程

協程可以把一個計算或操做,分解成若干步,而且能夠在任何一步停下來,並在須要的時候繼續執行剩下的步驟。這樣的模型給予了更細粒度的控制一個操做或是功能, 好比, 一個很是耗時間的操做, 被分步執行能夠更好的控制程序響應. 好比, 一個操做須要依賴各類條件, 能夠更好的處理條件不知足的時候的狀況. 也可以更好的把操做或是計算過程當中的狀態變化, 與其餘的狀態變化交互, 然而, 程序運行的過程就是抽象數據結構和結構不斷變化的過程, 協程可以優雅天然的進行這個變化過程的需求.併發

Unity 在每一幀都會去處理 GameObject 裏帶有的 Coroutine Function, 直到 Coroutine Function 被執行完畢. 當一個 Coroutine 開始啓動時, 它會執行到遇到 yield 爲止, 遇到 yield 的時候 Coroutine 會暫停執行, 直到知足 yield 語句的條件, 會開始執行 yield 語句後面的內容, 直到遇到下一個 yield 爲止 ... 如此循環直到整個函數結束, 這就是能夠將一個函數分割到多個幀裏去執行的思想.異步

也就是說, Coroutines 最棒的就是函數的執行能夠不在一次 Frame 裏完成, 能夠在多個 Frame 中完成. 好比, 咱們但願看到物體透明度的改變, 若是讓 color.a 的變化在一幀內完成, 那麼咱們是看不出來這其中的變化得, 由於太快, 因此讓 color.a 的變化必須在多個幀內完成, 咱們纔有可能用肉眼看到這一變化的過程.函數

Unity 的協程系統基於 C# 的接口 : IEnumerator, 容許爲本身的集合類型編寫枚舉類.ui

Unity 函數執行圖

4. 協程與線程的區別 #

  1. 歷史上先有的協程, 是操做系統用來模擬多任務併發, 協程是非搶佔式的, 多任務時間片不能公平分享, 線程是搶佔式的;url

  2. 線程能利用多核達到真正的並行計算, 若是任務設計的好, 線程能幾乎成倍的提升你的計算能力, 可是線程的缺點也很明顯, 那就是沒有設計好致使大量的鎖 | 切換 | 等待, 這些不少都是應用層的問題. 而協程由於是非搶佔式, 因此須要用戶本身釋放使用權來切換到其餘協程, 所以同一時間其實只有一個協程擁有運行權, 至關於單線程的能力.操作系統

  3. 協程相對線程的最大優勢就是 : 讓原來要使用異步 + 回調方式寫的非人類代碼, 能夠用看似同步的方式寫出來.

5. Unity 中協程相關的類 #

  1. yield return null; → 等待下一幀中的 Update 函數執行完以後再繼續執行;
  2. yield return new WaitForSeconds(10); → 延遲 10 秒後再繼續執行;
  3. yield return new WaitForFixedUpdate(); → 等待全部腳本中的 FixedUpdate 函數結束以後再繼續執行;
  4. yield return new WaitForEndOfFrame(); → 等待該幀中全部 Camera 和 GUI 對象渲染完畢, 在幀被顯示到屏幕以前恢復執行前面的代碼;
  5. yield return new WWW(url); → 等待 url 下載完後再繼續執行;
  6. yield return StartCoroutine(MyFunc()); → 等待協程結束後再執行;

6. 須要注意的幾點 #

- 協程是 C# 線程的替代品, 是 Unity 不使用線程的解決方案. 可是, 協程不是線程, 不能進行異步執行, 協程和 MonoBehaviour 的 Update 函數同樣也是在 MainThread 中執行的.

- 使用協程不用考慮同步和鎖的問題.

- 在程序中調用 StopCoroutine() 方法只能終止以字符串形式啓動(開始)的協程;

- 多個協程能夠同時運行,它們會根據各自的啓動順序來更新;

- 協程能夠嵌套任意多層;

- 若是你想讓多個腳本訪問一個協程,那麼你能夠定義靜態的協程;

- 協程不是多線程(儘管它們看上去是這樣的),它們運行在同一線程中,跟普通的腳本同樣;

- 若是你的程序須要進行大量的計算,那麼能夠考慮在一個隨着時間進行的協程中處理它們;

- IEnumerator 類型的方法不能帶 ref 或者 out 型的參數,但能夠帶被傳遞的引用;

- 目前在 Unity 中沒有簡便的方法來檢測做用於對象的協程數量以及具體是哪些協程做用在對象上。

- 協程能夠減小 callback 的使用, 可是不能徹底替換 callback. 基於事件驅動的編程裏面反而不能發揮協程的做用, 而用 callback 更適合. 想象一下用協程來寫 GUI 的事件處理你怎麼寫, 計算密集型的異步代碼裏面也只能用 callback. 而 NodeJs 那種 io 瓶頸單任務流程用協程的確很適合, 可是也須要 callback 做補充.

- 狀態機用協程其實也有問題, 好比狀態裏面嵌套子狀態, 再由子狀態切換到其餘狀態的子狀態, 開銷和代碼都會變差, 反而不如經典的狀態機簡單明瞭高效.

7. 幾條準則 #

  1. 協程的返回值必須是 IEnumerator;
  2. 協程的參數不能加關鍵字 ref 或 out;
  3. 在 C# 腳本中, 必須經過 StartCoroutine 來啓動協程;
  4. yield 語句要用 yield return 來代替;
  5. 在函數 Update 和 FixedUpdate 中不能使用 yield 語句, 可是能夠啓動協程;
  6. yield return 語句不能位於 try-catch 語句塊中, 但能夠位於 try-finally 的 try 語句塊中;
  7. yield return 語句不能放在匿名方法中;
  8. yield return 語句不能放在 unsafe 語句塊中;

8. 本文參考來源 #

Book <<Unity Case Study Manual>>
http://blog.csdn.net/u010153703/article/details/38557237
http://blog.csdn.net/huang9012/article/details/38492937
https://www.zhihu.com/question/20511233?rf=23290260
https://www.zhihu.com/question/20511233

End.

相關文章
相關標籤/搜索