Unity多線程(Thread)和主線程(MainThread)交互使用類——Loom工具分享


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);   }     }    void OnDisable()  {   if (_current == this)   {       _current = null;   }  }    // Use this for initialization  void Start()  {   }   List<Action> _currentActions = new List<Action>();   // Update is called once per frame  void Update()  {   lock (_actions)   {    _currentActions.Clear();    _currentActions.AddRange(_actions);    _actions.Clear();   }   foreach(var a in _currentActions)   {    a();   }   lock(_delayed)   {    _currentDelayed.Clear();    _currentDelayed.AddRange(_delayed.Where(d=>d.time <= Time.time));    foreach(var item in _currentDelayed)     _delayed.Remove(item);   }   foreach(var delayed in _currentDelayed)   {    delayed.action();   }        } }

       怎么實現一個函數內使用多線程計算又保持函數體內代碼的順序執行,印象中使用多線程就是要擺脫代碼塊的順序執行,但這里是把原本一個函數分拆成為兩部分:一部分在C#線程中使用,另一部還是得在Unity的MainThread中使用,怎么解決呢,還得看例子:

//Scale a mesh on a second thread
void ScaleMesh(Mesh mesh, float scale) {  //Get the vertices of a mesh  var vertices = mesh.vertices;  //Run the action on a new thread  Loom.RunAsync(()=>{   //Loop through the vertices   for(var i = 0; i < vertices.Length; i++)   {    //Scale the vertex    vertices[i] = vertices[i] * scale;   }   //Run some code on the main thread   //to update the mesh   Loom.QueueOnMainThread(()=>{    //Set the vertices    mesh.vertices = vertices;    //Recalculate the bounds    mesh.RecalculateBounds();   });  }); }

        這個例子是對Mesh的頂點進行放縮,同時也是一個使用閉包(closure)和lambda表達式的一個很好例子。看完例子,是不是很有把項目中一些耗時的函數給拆分出來,D.S.Qiu就想用這個方法來改進下NGUI的底層機制(看下性能不能改進)。

小結:

       D.S.Qiu在編程技術掌握還是一個菜鳥,Thread還是停留在實現Runable接口或繼承Thread的一個水平上,對多線程編程的認識還只是九牛一毛。本來我以為Loom的實現會比較復雜,當我發現只有100多行的代碼是大為驚嘆,這也得益於現在語言的改進,至少從語言使用的便利性上還是有很大的進步的。

       有了Loom這個工具類,在很多涉及UnityEngine對象的耗時計算還是可以得到一個解決方法的:

               如在場景中用A*算法進行大量的數據計算

               變形網格中操作大量的頂點 
               持續的要運行上傳數據到服務器 
               二維碼識別等圖像處理

        Loom簡單而又巧妙,佩服Loom的作者。

        如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM