對 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) 會掛起, 直到條件滿足才會被喚醒繼續執行后面代碼的一種函數.

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, 允許為自己的集合類型編寫枚舉類.

Unity 函數執行圖

4. 協程與線程的區別 #

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

  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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM