前言:
協程在Unity
中是一個很重要的概念,我們知道,在使用Unity
進行遊戲開發時,一般(注意是一般)不考慮多執行緒,那麼如何處理一些在主任務之外的需求呢,Unity
給我們提供了協程這種方式
為啥在Unity中一般不考慮多執行緒
- 因為在
Unity
中,只能在主執行緒中獲取物體的元件、方法、物件,如果脫離這些,Unity
的很多功能無法實現,那麼多執行緒的存在與否意義就不大了
既然這樣,執行緒與協程有什麼區別呢:
- 對於協程而言,同一時間只能執行一個協程,而執行緒則是併發的,可以同時有多個執行緒在執行
- 兩者在記憶體的使用上是相同的,共享堆,不共享棧
其實對於兩者最關鍵,最簡單的區別是微觀上執行緒是並行的,而協程是序列的,如果你不理解沒有關係,通過下面的解釋你就明白了
關於協程
1,什麼是協程
協程,從字面意義上理解就是協助程式的意思,我們在主任務進行的同時,需要一些分支任務配合工作來達到最終的效果
稍微形象的解釋一下,想象一下,在進行主任務的過程中我們需要一個對資源消耗極大的操作時候,如果在一幀中實現這樣的操作,遊戲就會變得十分卡頓,這個時候,我們就可以通過協程,在一定幀內完成該工作的處理,同時不影響主任務的進行
2,協程的原理
首先需要了解協程不是執行緒,協程依舊是在主執行緒中進行
然後要知道協程是通過迭代器來實現功能的,通過關鍵字IEnumerator來定義一個迭代方法,注意使用的是IEnumerator,而不是IEnumerable:
兩者之間的區別:
- IEnumerator:是非泛型的,也是協程認可的引數
- IEnumerable:通過泛型實現的迭代器,協程不使用該迭代器
在迭代器中呢,最關鍵的是yield 的使用,這是實現我們協程功能的主要途徑,通過該關鍵方法,可以使得協程的執行暫停、記錄下一次啟動的時間與位置等等:
關於迭代器的具體解釋:
- 可以參考:C#官方文件關於迭代器的具體描述
由於yield
在協程中的特殊性,與關鍵性,我們到後面在單獨解釋,先介紹一下協程如何通過程式碼實現
3、協程的使用
首先通過一個迭代器定義一個返回值為IEnumerator
的方法,然後再程式中通過StartCoroutine
來開啟一個協程即可:
在正式開始程式碼之前,需要了解StartCoroutine
的兩種過載方式:
StartCoroutine(string methodName
:這種是沒有引數的情況,直接通過方法名(字串形式)來開啟協程StartCoroutine(IEnumerator routine
:通過方法形式呼叫StartCoroutine(string methodName,object values)
:帶引數的通過方法名進行呼叫
協程開啟的方式主要是上面的三種形式,如果你還是不理解,可以檢視下面程式碼:
//通過迭代器定義一個方法
IEnumerator Demo(int i)
{
//程式碼塊
yield return 0;
//程式碼塊
}
//在程式種呼叫協程
public void Test()
{
//第一種與第二種呼叫方式,通過方法名與引數呼叫
StartCoroutine("Demo", 1);
//第三種呼叫方式, 通過呼叫方法直接呼叫
StartCoroutine(Demo(1));
}
在一個協程開始後,同樣會對應一個結束協程的方法StopCoroutine
與StopAllCoroutines
兩種方式,但是需要注意的是,兩者的使用需要遵循一定的規則,在介紹規則之前,同樣介紹一下關於StopCoroutine
過載:
StopCoroutine(string methodName
:通過方法名(字串)來進行StopCoroutine(IEnumerator routine
:通過方法形式來呼叫StopCoroutine(Coroutine routine)
:通過指定的協程來關閉
剛剛我們說到他們的使用是有一定的規則的,那麼規則是什麼呢,答案是前兩種結束協程方法的使用上,如果我們是使用StartCoroutine(string methodName)
來開啟一個協程的,那麼結束協程就只能使用StopCoroutine(string methodName)
和StopCoroutine(Coroutine routine)
來結束協程,可以在文件中找到這句話:
4、關於yield
在上面,我們已經知道yield
的關鍵性,要想理解協程,就要理解yield
如果你瞭解Unity
的指令碼的生命週期,你一定對yield
這幾個關鍵詞很熟悉,沒錯,yield
也是指令碼生命週期的一些執行方法,不同的yield
的方法處於生命週期的不同位置,可以通過下圖檢視:
通過這張圖可以看出大部分yield
位置Update
與LateUpdate
之間,而一些特殊的則分佈在其他位置,這些yield
代表什麼意思呢,又為啥位於這個位置呢
首先解釋一下位於Update
與LateUpdate
之間這些yield 的含義:
-
yield return null
; 暫停協程等待下一幀繼續執行 -
yield return 0或其他數字
; 暫停協程等待下一幀繼續執行 -
yield return new WairForSeconds(時間)
; 等待規定時間後繼續執行 -
yield return StartCoroutine("協程方法名")
;開啟一個協程(巢狀協程)
在瞭解這些yield的方法後,可以通過下面的程式碼來理解其執行順序:
void Update()
{
Debug.Log("001");
StartCoroutine("Demo");
Debug.Log("003");
}
private void LateUpdate()
{
Debug.Log("005");
}
IEnumerator Demo()
{
Debug.Log("002");
yield return 0;
Debug.Log("004");
}
將上面的指令碼掛載到物體上,執行遊戲場景,來檢視列印的日誌,可以看到下面的日誌記錄:
可以很清晰的看出,協程雖然是在Update
中開啟,但是關於yield return null
後面的程式碼會在下一幀執行,並且是在Update執行完之後才開始執行,但是會在LateUpdate
之前執行
接下來看幾個特殊的yield
,他們是用在一些特殊的區域,一般不會有機會去使用,但是對於某些特殊情況的應對會很方便
yield return GameObject
; 當遊戲物件被獲取到之後執行yield return new WaitForFixedUpdate()
:等到下一個固定幀數更新yield return new WaitForEndOfFrame()
:等到所有相機畫面被渲染完畢後更新yield break
; 跳出協程對應方法,其後面的程式碼不會被執行
通過上面的一些yield
一些用法以及其在指令碼生命週期中的位置,我們也可以看到關於協程不是執行緒的概念的具體的解釋,所有的這些方法都是在主執行緒中進行的,只是有別於我們正常使用的Update
與LateUpdate
這些可視的方法
5、執行緒幾個小用法
5.1、將一個複雜程式分幀執行:
如果一個複雜的函式對於一幀的效能需求很大,我們就可以通過yield return null
將步驟拆除,從而將效能壓力分攤開來,最終獲取一個流暢的過程,這就是一個簡單的應用
舉一個案例,如果某一時刻需要使用Update
讀取一個列表,這樣一般需要一個迴圈去遍歷列表,這樣每幀的程式碼執行量就比較大,就可以將這樣的執行放置到協程中來處理:
public class Test : MonoBehaviour
{
public List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6 };
private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(PrintNum(nums));
}
}
//通過協程分幀處理
IEnumerator PrintNum(List<int> nums)
{
foreach(int i in nums)
{
Debug.Log(i);
yield return null;
}
}
}
上面只是列舉了一個小小的案例,在實際工作中會有一些很消耗效能的操作的時候,就可以通過這樣的方式來進行效能消耗的分消
5.2、進行計時器工作
當然這種應用場景很少,如果我們需要計時器有很多其他更好用的方式,但是你可以瞭解是存在這樣的操作的,要實現這樣的效果,需要通過yield return new WaitForSeconds()
的延時執行的功能:
IEnumerator Test()
{
Debug.Log("開始");
yield return new WaitForSeconds(3);
Debug.Log("輸出開始後三秒後執行我");
}
5.3、非同步載入等功能
只要一說到非同步,就必定離不開協程,因為在非同步載入過程中可能會影響到其他任務的程式,這個時候就需要通過協程將這些可能被影響的任務剝離出來
常見的非同步操作有:
AB
包資源的非同步載入Reaources
資源的非同步載入- 場景的非同步載入
WWW
模組的非同步請求
這些非同步操作的實現都需要協程的支援,可以通過我之前的一篇場景載入介面實現的文章來理解該內容:
關於非同步的文章:
總結
通過上面的一些操作,相信你應該理解協程的基本原理與用法,以及一些相關的小知識
因為協程本身也是一個比較複雜的概念,所以我的理解也可能有錯誤的地方,如果你發現文章中有哪些不正確的地方,歡迎留言指出< ^ _ ^ >