在上一回合談到,客戶端應用程序的全部操做都在主線程上進行,因此一些比較耗時的操做能夠在異步線程上去進行,充分利用CPU的性能來達到程序的最佳性能。對於Unity而言,又提供了另一種『異步』的概念,就是協程(
Coroutine
),經過反編譯,它本質上仍是在主線程上的優化手段,並不屬於真正的多線程(Thread
)。那麼問題來了,怎樣在Unity中使用多線程呢?git
雖然這不是什麼難點,但我以爲仍是有必要提一下多線程編程幾個值得注意的事項:github
在Unity中建立一個異步線程是很是簡單的,直接使用類System.Threading.Thread
就能夠建立一個線程,線程啓動以後畢竟要幫咱們去完成某件事情。在編程領域,這件事就能夠描述了一個方法,因此須要在構造函數中傳入一個方法的名稱。編程
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork)
workerThread.Start();複製代碼
線程啓動很簡單,那麼線程終止呢,是否是調用Abort
方法。不是,雖然Thread
對象提供了Abort
方法,但並不推薦使用它,由於它並不會立刻中止,若是涉及非託管代碼的調用,還須要等待非託管代碼的處理結果。緩存
通常中止線程的方法是爲線程設定一個條件變量,在線程的執行方法裏設定一個循環,並以這個變量爲判斷條件,若是爲
false
則跳出循環,線程結束。安全
public class Worker
{
public void DoWork() {
while (!_shouldStop)
{
Console.WriteLine("worker thread: working...");
}
Console.WriteLine("worker thread: terminating gracefully.");
}
public void RequestStop() {
_shouldStop = true;
}
private volatile bool _shouldStop;
}複製代碼
因此,你能夠在應用程序退出(OnApplicationQuit
)時,將_shouldStop
設置爲true
來到達線程的安全退出。多線程
多線程最麻煩的一點就是共享數據的處理了,想象一下A,B兩個線程同一時刻處理一個變量,它最終的值究竟是什麼。因此通常須要使用lock
,但C#提供了另外一個關鍵字volatile
,告訴CPU不讀緩存直接把最新的值返回。因此_shouldStop
被volatile
修飾。異步
是否是以爲多線程好簡單,好像也沒想象的那麼複雜,當你愉快的在多線程中訪問UI控件時,Duang~~~,一個錯誤告訴你,不能在異步線程訪問UI控件。這是確定的,跨線程訪問UI控件是不安全的,理應被禁止。那怎麼辦呢?函數
若是你有其餘客戶端的開發經驗,好比iOS或者WPF經驗,確定知道Dispatcher。Dispatcher翻譯過來就是調度員的意思,簡單理解就是每一個線程都有惟一的調度員,那麼主線程就有主線程的調度員,實際上咱們的代碼最終也是交給調度員去執行,因此要去訪問UI線程上的控件,咱們能夠間接的向調度員發出命令。工具
因此在WPF中,跨線程訪問UI控件通常的寫法以下:性能
Thread thread=new Thread(()=>{
this.Dispatcher.Invoke(()=>{
//UI
this.textBox.text=...
this.progressBar.value=...
});
});複製代碼
嗯~ o( ̄▽ ̄)o,不錯,但尷尬的是Unity沒有提供Dispatcher啊!
對,但咱們能夠本身實現,把握住幾個關鍵點:
Update
方法,每一幀會執行因此自定義的UnityDispatcher
提供一個BeginInvoke
方法,並接送一個Action
public void BeginInvoke(Action action){
while (true) {
//以原子操做的形式,將 32 位有符號整數設置爲指定的值並返回原始值。
if (0 == Interlocked.Exchange (ref _lock, 1)) {
//acquire lock
_wait.Enqueue(action);
_run = true;
//exist
Interlocked.Exchange (ref _lock,0);
break;
}
}
}複製代碼
這是一個生產者,向隊列裏添加須要處理的Action。有了生產者以後,還須要消費者,Unity中的Update
就是一個消費者,每一幀都會執行,因此若是隊列裏有任務,它就執行
void Update() {
if (_run) {
Queue<Action> execute = null;
//主線程不推薦使用lock關鍵字,防止block 線程,以致於deadlock
if (0 == Interlocked.Exchange (ref _lock, 1)) {
execute = new Queue<Action>(_wait.Count);
while(_wait.Count!=0){
Action action = _wait.Dequeue ();
execute.Enqueue (action);
}
//finished
_run=false;
//release
Interlocked.Exchange (ref _lock,0);
}
//not block
if (execute != null) {
while (execute.Count != 0) {
Action action = execute.Dequeue ();
action ();
}
}
}
}複製代碼
值得注意的是,Queue
不是線程安全的,因此須要鎖,我使用了Interlocked.Exchange
,好處是它以原子的操做來執行而且還不會阻塞線程,由於主線程自己任務繁重,因此我不推薦使用lock
。
到目前爲止,相信你對Coroutine
和Thread
有清楚的認識,但它們並非互斥的,能夠混合使用,好比Coroutine
等待異步線程返回結果,假設異步線程裏執行的是很是複雜的AI操做,這顯然放在主線程會很是繁重。
因爲篇幅有限,我不貼完整代碼了,只分析其中最核心思路:
在Thread
中有一個WaitFor
方法,它每一幀都會詢問異步任務是否完成:
public bool Update(){
if(_isDown){
OnFinished ();
return true;
}
return false;
}
public IEnumerator WaitFor(){
while(!Update()){
//暫停協同程序,下一幀再繼續往下執行
yield return null;
}
}複製代碼
那麼在某一個UI線程中,等待異步線程的結果,注意利用StartCouroutine
,此等待並不是阻塞線程,相信你已經它內部的機制了。
void Start(){
Debug.Log("Main Thread :"+Thread.CurrentThread.ManagedThreadId+" work!");
StartCoroutine (Move());
}
IEnumerator Move() {
pinkRect.transform.DOLocalMoveX(250, 1.0f);
yield return new WaitForSeconds(1);
pinkRect.transform.DOLocalMoveY(-150, 2);
yield return new WaitForSeconds(2);
//AI操做,陷入深思,在異步線程執行,GreenRect不會卡頓
job.Start();
yield return StartCoroutine (job.WaitFor());
pinkRect.transform.DOLocalMoveY(150, 2);
}複製代碼
這兩篇文章爲你們介紹了怎樣在Unity中使用協程和多線程,多線程其實不難,但同步數據是最麻煩的。Coroutine實際上就是IEnumerator
和yield
這兩個語法糖讓咱們很難理解其中的奧祕,推薦使用反編譯工具去查看,相信你會豁然開朗。
源代碼託管在Github上,點擊此瞭解
歡迎關注個人公衆號: