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

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

By D.S.Qiu 編程

尊重他人的勞動,支持原創,轉載請註明出處:http.dsqiu.iteye.com api

          熟悉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; oop

       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)交流,您的鼓勵和支持是我前進的動力,但願能有更多更好的分享。

相關文章
相關標籤/搜索