一、協(xié)程
協(xié)程其實(shí)就是一個(gè)IEnumerator(迭代器),
IEnumerator 接口有兩個(gè)方法 Current 和 MoveNext()
Unity在每幀做的工作就是:調(diào)用 協(xié)程(迭代器)MoveNext() 方法,如果返回 true ,就從當(dāng)前位置繼續(xù)往下執(zhí)行。
協(xié)程是什么?
協(xié)同程序,即在主程序運(yùn)行時(shí)同時(shí)開啟另一段邏輯處理,來協(xié)同當(dāng)前程序的執(zhí)行。換句話說,開啟協(xié)同程序就是開啟一個(gè)線程。
協(xié)程是一個(gè)分部執(zhí)行,遇到條件(yield return 語句)會(huì)掛起,直到條件滿足才會(huì)被喚醒繼續(xù)執(zhí)行后面的代碼。通俗點(diǎn)說:程序內(nèi)部可中斷,然后轉(zhuǎn)而執(zhí)行別的子程序,在適當(dāng)?shù)臅r(shí)候再返回來接著執(zhí)行。
協(xié)程的運(yùn)行
協(xié)程不是線程,也不是異步執(zhí)行的。協(xié)程和 MonoBehaviour 的 Update函數(shù)一樣也是在MainThread中執(zhí)行的。
協(xié)同程序可以和主程序并行運(yùn)行,和多線程有點(diǎn)類似。
協(xié)程的作用
1、延時(shí)(等待)一段時(shí)間執(zhí)行代碼;
2、等某個(gè)操作完成之后再執(zhí)行后面的代碼。
總結(jié)起來就是一句話:控制代碼在特定的時(shí)機(jī)執(zhí)行。

WaitForSeconds()受Time.timeScale影響,當(dāng)Time.timeScale = 0f 時(shí),yield return new WaitForSecond(x) 將不會(huì)滿足。
練習(xí)測試
void Start () {
print("開始下載" + "---" + Time.time);
StartCoroutine(waitOne());
print("正在下載----50%" + "---" + Time.time);
}
IEnumerator waitOne()
{
Debug.Log("加入下載列表" + "---" + Time.time);
yield return StartCoroutine(waitTwo());
print("下載完成" + "---" + Time.time);
}
IEnumerator waitTwo()
{
print("正在下載----1%" + "---" + Time.time);
yield return new WaitForSeconds(3);
print("正在下載----70%" + "---" + Time.time);
}
打印結(jié)果(執(zhí)行順序):

禁用該腳本協(xié)程是否還會(huì)執(zhí)行?

二、WaitUntil和WaitWhile的秘密
開始學(xué)習(xí)WaitUntil:
θ 根據(jù)定義,它掛起語句,直到指定的條件返回true。
θ 換句話說,當(dāng)我們指定的條件是false時(shí),它不會(huì)繼續(xù)執(zhí)行語句。Unity將會(huì)等待條件返回true。
測試
public int counter;
void Start ()
{
counter = 0;
StartCoroutine(FuelNotification());
}
IEnumerator FuelNotification()
{
Debug.Log("開始通關(guān)" + "---" + Time.time);
yield return new WaitUntil(IsTankEmpty);
Debug.Log("通關(guān)了!" + "---" + Time.time);
}
void Update()
{
if (counter<21)
{
Debug.Log("通關(guān)數(shù)量:"+counter+"---"+Time.time);
counter++;
}
}
public bool IsTankEmpty()
{
if (counter<21)
{
return false;
}
else
{
return true;
}
}
打印結(jié)果:

下面我們?cè)賮砜纯丛趺词褂肔ambda表達(dá)式。Lambda表達(dá)式是一個(gè)非常便利的工具,試一試吧。
public int counter;
void Start()
{
counter = 20;
StartCoroutine(FuelNotification());
}
void Update()
{
if (counter > 0)
{
Debug.Log("Fuel Level:" + counter + "---" + Time.time);
counter--;
}
}
IEnumerator FuelNotification()
{
Debug.Log("Waiting for tank to get empty" + "---" + Time.time);
yield return new WaitUntil(() => counter <= 0);
Debug.Log("Tank Empty!" + "---" + Time.time);
}
打印結(jié)果 :

現(xiàn)在來學(xué)習(xí)WaitWhile:WaitWhile是WaitUntil的對(duì)立面。它抑制表達(dá)式,當(dāng)條件為真。不像WaitUntil,它將會(huì)等待條件變?yōu)閒alse,才能指向后面的代碼塊。和前面同樣的例子,我們使用WaitWhile:我們只需要改變Lambda表達(dá)式,讓其等于False,其打印結(jié)果是一樣的。
public int counter;
void Start()
{
counter = 20;
StartCoroutine(FuelNotification());
}
void Update()
{
if (counter > 0)
{
Debug.Log("Fuel Level:" + counter + "---" + Time.time);
counter--;
}
}
IEnumerator FuelNotification()
{
Debug.Log("Waiting for tank to get empty" + "---" + Time.time);
yield return new WaitUntil(() => counter > 0);
Debug.Log("Tank Empty!" + "---" + Time.time);
}

三、線程與協(xié)程的區(qū)別
線程
線程之間共享變量,解決了通訊麻煩的問題,但是對(duì)于變量的訪問需要鎖,線程的調(diào)度主要也是有操作系統(tǒng)完成,一個(gè)進(jìn)程可以擁有多個(gè)線程,但是其中每個(gè)線程會(huì)共享父進(jìn)程向操作系統(tǒng)申請(qǐng)資源,這個(gè)包括虛擬內(nèi)存、文件等,由于是共享資源,所以創(chuàng)建線程所需要的系統(tǒng)資源占用比進(jìn)程小很多,相應(yīng)的可創(chuàng)建的線程數(shù)量也變得相對(duì)多很多。線程時(shí)間的通訊除了可以使用進(jìn)程之間通訊的方式以外還可以通過共享內(nèi)存的方式進(jìn)行通信,所以這個(gè)速度比通過內(nèi)核要快很多。另外在調(diào)度方面也是由于內(nèi)存是共享的,所以上下文切換的時(shí)候需要保存的東西就像對(duì)少一些,這樣一來上下文的切換也變得高效。
協(xié)程
協(xié)程的調(diào)度完全由用戶控制,一個(gè)線程可以有多個(gè)協(xié)程,用戶創(chuàng)建了幾個(gè)線程,然后每個(gè)線程都是循環(huán)按照指定的任務(wù)清單順序完成不同的任務(wù),當(dāng)任務(wù)被堵塞的時(shí)候執(zhí)行下一個(gè)任務(wù),當(dāng)恢復(fù)的時(shí)候再回來執(zhí)行這個(gè)任務(wù),任務(wù)之間的切換只需要保存每個(gè)任務(wù)的上下文內(nèi)容,就像直接操作棧一樣的,這樣就完全沒有內(nèi)核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快;另外協(xié)程還需要保證是非堵塞的且沒有相互依賴,協(xié)程基本上不能同步通訊,多采用一步的消息通訊,效率比較高。