前言:
協程在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
模塊的異步請求
這些異步操作的實現都需要協程的支持,可以通過我之前的一篇場景加載界面實現的文章來理解該內容:
關於異步的文章:
總結
通過上面的一些操作,相信你應該理解協程的基本原理與用法,以及一些相關的小知識
因為協程本身也是一個比較復雜的概念,所以我的理解也可能有錯誤的地方,如果你發現文章中有哪些不正確的地方,歡迎留言指出< ^ _ ^ >