Unity多線程(Thread)和主線程(MainThread)交互使用類——Loom工具分享
By D.S.Qiu
尊重他人的勞動,支持原創,轉載請注明出處:http.dsqiu.iteye.com
熟悉Unity的developer都知道在Unity中的線程不能使用Unity的對象,但可以使用Unity的值類型變量,如Vector3等。這樣就使得線程在Unity中顯的很雞肋和蹩腳,因為很多函數很都是UnityEngine類或函數的調用的,對於哪些是可以在多線程使用,風雨沖進行了如下總結:
0. 變量(都能指向相同的內存地址)都是共享的
1. 不是UnityEngine的API能在分線程運行
2. UnityEngine定義的基本結構(int,float,Struct定義的數據類型)可以在分線程計算,如 Vector3(Struct)可以 , 但Texture2d(class,根父類為Object)不可以。
3. UnityEngine定義的基本類型的函數可以在分線程運行,如
int i = 99;
print (i.ToString());
Vector3 x = new Vector3(0,0,9);
x.Normalize();
類的函數不能在分線程運行
obj.name
實際是get_name函數,分線程報錯誤:get_name can only be called from the main thread.
Texture2D tt = new Texture2D(10,10);
實際會調用UnityEngine里的Internal_Create,分線程報錯誤:Internal_Create can only be called from the main thread.
其他transform.position,Texture.Apply()等等都不能在分線程里運行。
結論: 分線程可以做 基本類型的計算, 以及非Unity(包括.Net及SDK)的API。
D.S.Qiu覺得Unity做了這個限制,主要是Unity的函數執行機制是幀序列調用,甚至連Unity的協程Coroutine的執行機制都是確定的,如果可以使用多線程訪問UnityEngine的對象和api就得考慮同步問題了,也就是說Unity其實根本沒有多線程的機制,協程只是達到一個延時或者是當指定條件滿足是才繼續執行的機制。
我們的項目目前還有沒有比較耗時的計算,所以還沒有看到Thread的使用。本來一直沒有太考慮着方面的事情,直到在UnityGems.com看到Loom這個類,嘆為觀止呀。直接貼出人家的介紹(沒必要翻譯了 ):
Threads on a Loom
Our class is called Loom. Loom lets you easily run code on another thread and have that other thread run code on the main game thread when it needs to.
There are only two functions to worry about:
- RunAsync(Action) which runs a set of statements on another thread
- QueueOnMainThread(Action, [optional] float time) - which runs a set of statements on the main thread (with an optional delay).
You access Loom using Loom.Current - it deals with creating an invisible game object to interact with the games main thread.
我們只需要關系兩個函數:RunAsync(Action)和QueueOnMainThread(Action, [optional] float time) 就可以輕松實現一個函數的兩段代碼在C#線程和Unity的主線程中交叉運行。原理也很簡單:用線程池去運行RunAsync(Action)的函數,在Update中運行QueueOnMainThread(Acition, [optional] float time)傳入的函數。
直接貼出源碼,供拜讀:
using UnityEngine;
using System.Collections; using System.Collections.Generic; using System; using System.Threading; using System.Linq; public class Loom : MonoBehaviour { public static int maxThreads = 8; static int numThreads; private static Loom _current; private int _count; public static Loom Current { get { Initialize(); return _current; } } void Awake() { _current = this; initialized = true; } static bool initialized; static void Initialize() { if (!initialized) { if(!Application.isPlaying) return; initialized = true; var g = new GameObject("Loom"); _current = g.AddComponent<Loom>(); } } private List<Action> _actions = new List<Action>(); public struct DelayedQueueItem { public float time; public Action action; } private List<DelayedQueueItem> _delayed = new List<DelayedQueueItem>(); List<DelayedQueueItem> _currentDelayed = new List<DelayedQueueItem>(); public static void QueueOnMainThread(Action action) { QueueOnMainThread( action, 0f); } public static void QueueOnMainThread(Action action, float time) { if(time != 0) { lock(Current._delayed) { Current._delayed.Add(new DelayedQueueItem { time = Time.time + time, action = action}); } } else { lock (Current._actions) { Current._actions.Add(action); } } } public static Thread RunAsync(Action a) { Initialize(); while(numThreads >= maxThreads) { Thread.Sleep(1); } Interlocked.Increment(ref numThreads); ThreadPool.QueueUserWorkItem(RunAction, a); return null; } private static void RunAction(object action) { try { ((Action)action)(); } catch { } finally { Interlocked.Decrement(ref numThreads); } }