Unity協程的概念:
協程存在于許多編程語言中,Unity3D在呼叫我們撰寫的C#腳本時,會將它們統一放在一條主執行緒當中調度,所有的游戲物件、游戲組件都在這條主執行緒中,其他的執行緒并不能訪問這些資料,所以對于我們所寫的所有腳本來說,Unity是單執行緒的,
既然Unity3D不能多執行緒,那肯定需要一種機制來模擬多執行緒,來解決這種問題,這個機制便是協程,
要理解什么是協程,先讓我們看看迭代器:
迭代器:
讓我們先來看看下面的代碼
List<int> arr = new List<int>(){ 0, 1, 2, 3, 4 };
foreach (int i in arr){
Debug.log(i);
}
不知道各位有沒有想過,這個foreach到底做了什么,arr又是因為什么,能夠遍歷這個陣列中的所有元素?
讓我們查看List<>的元資料:
public class List<T> : ICollection<T>, IEnumerable<T>, IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList
List這個泛型類繼承了很多的介面,有一個貌似與我們要探討的問題有關—— IEnumerable和 IEnumerable<T>,
讓我們繼續深入,看看它們的代碼:
IEnumerable:
using System.Runtime.InteropServices;
namespace System.Collections
{
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
}
我們可以看到,繼承了這個介面的類必須實作一個方法——回傳一個IEnumerator的方法,這個IEnumerator又是什么呢?繼續深入下去:
這是IEnumerator的代碼
namespace System.Collections
{
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
}
IEnumerator又是一個介面,函式回傳了一個介面,事情有點開始繞了,這個介面中有一個Current、一個回傳bool型的MoveNext方法、還有個Reset()方法
此處我查閱了微軟官方的檔案,有對IEnumerator介面的詳細解釋,
IEnumerator官方檔案解釋

看了這段文字,可能還是有很多人云里霧里,不知所云,沒關系,讓我們把上面的例子詳細理解一下:
1.首先,微軟定義了一個Person類:
// Simple business object.
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
這個類有姓、名兩個字串欄位和一個建構式為它們初始化值,
2.接著,定義了一個People類,并讓他繼承IEnumerator介面
// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
// Implementation for the GetEnumerator method.
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
People這是Person的容器,內部一個Person型別的陣列_people,實作IEnumerator介面是為了讓他能夠被Foreach陳述句呼叫,建構式接收一個Person型別的陣列,并將其復制進_people中,至此都沒什么好留意的,
需要注意的是,這里出現了兩個GetEnumerator方法,上面的是實作介面之用,這個類中還是可以有一個與介面中方法同名的方法成員,
下面它實作了IEnumerator介面中的GetEnumerator()方法,回傳GetEnumerator,并將它強制轉換為IEnumerator介面,下面的GetEnumerator方法回傳的則是一個PeopleEnum物件,這個PeopleEnum又是什么呢,讓我們繼續往下看:
4.PeopleEnum類,實作了IEnumerator介面,離真相越來越近了
// When you implement IEnumerable, you must also implement IEnumerator.
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
這個類還和People類一樣,有一個Person型別的陣列,和一個為它初始化的建構式,下面實作了IEnumerator——MoveNext,Reset,Current兩個方法和一個屬性,結合上面官方檔案的注釋和博主一步一步的除錯,終于算是搞懂了這是怎么一回事,
PeopleEnum中有一個標記位置的引數position,默認為-1,呼叫MoveNext()方法時,會將這個位置值+1,然后判斷是否到了陣列的盡頭,并將判斷的結果回傳,如果沒有到陣列末尾,回傳true,表示可以繼續下一輪,一旦回傳false則停止遍歷,
Reset()方法便是直接將position重置為-1;
Current為只讀,回傳_people陣列中的第position位元素
5.這是Main()函式中的內容:
static void Main()
{
Person[] peopleArray = new Person[3]
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
}
首先是初始化一個Person陣列并將他賦值給People類中,接下來進入了foreach陳述句,程式首先是通過peopleList進入了People類的GetEnumerator方法中:
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
然后進入自己實作的GetEnumerator方法中:
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
將自身的_people陣列傳入并回傳,因為PeopleEnum本身實作了IEnumerator介面,所以將它的回傳時轉換成IEnumerator型別也是合理的,這就回傳了一個_people的列舉器,結合上面對該介面中三個成員的描述與下面的單步除錯程序,程式的運行邏輯便清晰明了了:
得到了列舉器之后,程式會首先進入MoveNext()方法,position++,為0,回傳ture,表示可以繼續遍歷,之后訪問Current屬性,回傳_people陣列的第0位元素“John Smith”將它賦值給p,然后把p列印出來,
然后再進入MoveNext方法,position++,為1,回傳true,可以繼續遍歷,Current回傳第1位元素,列印,
再次進入MoveNext方法,position++,為2,回傳true,可以繼續遍歷,Current回傳第2位元素,列印,
再次進入MoveNext方法,position++,為3,這時回傳false,此時退出foreach陳述句,
如果我們將MoveNext中的回傳值改為false,那么控制臺不會列印任何資訊,進一步驗證了我的想法,
總結一下思路:能被foreach陳述句遍歷的類必須繼承 IEnumerable,表示這個是一個可以被列舉的類,繼承該介面的類必須實作一個GetEnumerator方法,該方法回傳一個列舉器IEnumerator,foreach憑借其實作的MoveNext,Current便可以遍歷我們想要遍歷的內容啦,
大致的結構便是這樣的:

最后的最后,其實繼承Enumerator的并不一定要是一個額外的類,完全可以是一個自己的結構體成員,就像List的元資料那樣:
public List<T>.Enumerator GetEnumerator();
public struct Enumerator : IEnumerator<T>, IEnumerator, IDisposable
{
public T Current { get; }
public void Dispose();
public bool MoveNext();
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/303125.html
標籤:其他
下一篇:【Java】GUI實作貪吃蛇
