在進入本章主題以前,咱們必需要了解客戶端應用程序都是單線程模型,即只有一個主線程(Main Thread),或者叫作UI線程,即全部的UI控件的建立和操做都是在主線程上完成的。而服務器端應用程序,也就是咱們常見的Web應用程序每每是多線程的,故用戶A訪問勢必不會影響用戶B的訪問過程。因此對於Web應用而言,多線程的數據同步和併發的管理每每是個頭疼的問題。那麼對於客戶端應用程序而言,就一我的使用,還要須要考慮多線程嗎?git
這是個好問題,從設備的硬件上,這已不是瓶頸:github
學過操做系統的同窗確定知道CPU是真正的處理大腦,在單核的CPU年代,在某一時刻CPU只能處理一個線程,經過CPU的調度來實如今不一樣線程間切換工做。因爲CPU調度的時間很快,因此給人形成併發的假象。
隨着硬件的提高,多核CPU已是常態化了。好比雙核CPU而言,某一時刻能夠有2個線程並行計算。 數組
因此,是否須要在客戶端使用多線程技術,仍是取決於你的應用的複雜度:服務器
lock
致使主線block或者deadlock。ThreadPool
或者利用GC來回收線程。 回到本文的主題,對於Unity應用程序而言,還提供了另一種『異步方式』:Coroutine
。Coroutine
也就是協程的意思,只是看起來像多線程,它實際上並非,仍是在主線程上操做。網絡
Coroutine實際上由IEnumerator
接口以及一個或者多個的yield
語句構成的迭代器(iterator
)塊構成。多線程
枚舉器接口 IEnumerator
包含3個方法:併發
yield
是個比較晦澀的技術,緣由是編譯器幫咱們作了太多的工做(CompilerGenerate),致使咱們沒法理解到內部的實現。若是你去翻閱漢英詞典,你會對yield一頭霧水。我我的傾向將其翻譯成中斷和產出比較好,這也是yield單詞包含的意思,我下面也會闡述爲何要翻譯成這兩個意思。異步
深究yield
以前,我以爲應該略微瞭解一下爲何咱們能foreach
遍歷一個數組?this
緣由很簡單,數組Array它是一個可枚舉的類(enumerable),一個可枚舉類提供了一個枚舉器(enumerator),枚舉器能夠依次訪問數組裏的元素,也就是以前提過的
Current
屬性返回集合當前位置的對象。因此,我能夠模擬foreach
的實現,實際上foreach
內部實現也大體類似。spastatic void Main(string[] args) { string[] animals = {"dog", "cat", "pig"}; //獲取枚舉器 var ie = animals.GetEnumerator(); //移到下一項,默認的index=-1 while (ie.MoveNext()) { //得到當前項 Console.WriteLine(ie.Current); } Console.ReadLine(); }複製代碼
假設你是個C#新手,你得好好消化一下上述的邏輯,由於這是撥開迷霧的第一層:瞭解爲何可以枚舉一個集合。固然咱們也能夠建立本身的可被枚舉的類,須要爲它提供自定義的枚舉器,只需實現
IEnumerator
接口便可。值得注意的事,自建的可枚舉類同時也要實現IEnumerable
接口,該接口只提供一個方法:GetEnumerator()
,用來返回枚舉器。
建立自定義的枚舉類AnimalSet
:
class AnimalSet : IEnumerable
{
private readonly string[] _animals = {"the dog", "the pig", "the cat"};
public IEnumerator GetEnumerator() {
return new AnimalEnumerator(_animals);
}
}複製代碼
須要爲AnimalSet
提供自定義的枚舉器AnimalEnumerator
class AnimalEnumerator : IEnumerator
{
private string[] _animals;
private int _index = -1;
public AnimalEnumerator(string[] animals) {
_animals=new string[animals.Length];
for (var i = 0; i < animals.Length; i++)
{
_animals[i] = animals[i];
}
}
public bool MoveNext() {
_index++;
return _index<_animals.Length;
}
public void Reset() {
_index = -1;
}
public object Current
{
get { return _animals[_index]; }
}
}複製代碼
你可能會以爲奇怪,這和yield
又有什麼關係呢?要解惑yield
這是第二個階段:能知道枚舉器是怎樣工做的。
若是你很清楚上訴兩個階段的內部原理以後,要理解Unity中的Coroutine
是很是簡單的,你會了解爲何它是僞的「多線程」。
這是一段很是普通的代碼,司空見慣。
void Start() {
StartCoroutine(MyEnumerator());
Debug.Log("finish");
}
private IEnumerator MyEnumerator() {
Debug.Log("wait for 1s");
yield return new WaitForSeconds(1);
Debug.Log("wait for 2s");
yield return new WaitForSeconds(2);
Debug.Log("wait for 3s");
yield return new WaitForSeconds(3);
}複製代碼
注意到MyEnumerator
方法的放回類型了嗎?沒錯,返回的就是枚舉器,你會疑問,你沒有定義一個枚舉器而且實現了IEnumerator
接口啊!別急,問題就出在yield
上,C#爲了簡化咱們建立枚舉器的步驟,你想一想看你須要先實現IEnumerator
接口,而且實現Current
,MoveNext
,Reset
步驟。C#從2.0開始提供了有yield
組成的迭代器塊。編譯器會自動更具迭代器塊建立了枚舉器。不信,反編譯看看:
public class Test : MonoBehaviour
{
private IEnumerator MyEnumerator() {
UnityEngine.Debug.Log("wait for 1s");
yield return new WaitForSeconds(1f);
UnityEngine.Debug.Log("wait for 2s");
yield return new WaitForSeconds(2f);
UnityEngine.Debug.Log("wait for 3s");
yield return new WaitForSeconds(3f);
}
private void Start() {
base.StartCoroutine(this.MyEnumerator());
UnityEngine.Debug.Log("finish");
}
[CompilerGenerated]
private sealed class <MyEnumerator>d__1 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public Test <>4__this;
[DebuggerHidden]
public <MyEnumerator>d__1(int <>1__state)
{
this.<>1__state = <>1__state;
}
private bool MoveNext() {
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
UnityEngine.Debug.Log("wait for 1s");
this.<>2__current = new WaitForSeconds(1f);
this.<>1__state = 1;
return true;
case 1:
this.<>1__state = -1;
UnityEngine.Debug.Log("wait for 2s");
this.<>2__current = new WaitForSeconds(2f);
this.<>1__state = 2;
return true;
case 2:
this.<>1__state = -1;
UnityEngine.Debug.Log("wait for 3s");
this.<>2__current = new WaitForSeconds(3f);
this.<>1__state = 3;
return true;
case 3:
this.<>1__state = -1;
return false;
}
return false;
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
//...省略...
}
}複製代碼
有幾點能夠肯定:
yield
是個語法糖,編譯事後的代碼看不到yield
<MyEnumerator>d__1
yield return
被聲明爲枚舉時的下一項,即Current屬性,經過MoveNext方法來訪問結果OK,經過層層推動,想必你對Untiy中的協程有必定的瞭解了。再回過頭來,我將yield
翻譯成了中斷和產出,談談個人理解。
yield
構成的迭代塊是告訴編譯器如何建立枚舉器的行爲,反編譯獲得的結果能夠看到,它們的執行並非連續的,而是經過switch
來從一個狀態(state)跳轉到另外一個狀態yield
是和return
連用, yield return
以後的語句被編譯器賦值給current變量,最終經過Current
屬性產出枚舉項本文的初衷是想介紹如何在Unity中使用多線程,但協程每每是繞不開的話題,因而索性就剖析了下它,故決定單獨成一篇。本章內容對多線程開了個頭,我將在下篇文章中說說怎樣在Unity中使用和管理多線程。
源代碼託管在Github上,點擊此瞭解
歡迎關注個人公衆號: