Search Unity

Any experience using the Task Parallel Library with unity?

Discussion in 'Scripting' started by ThatGuyFromTheWeb, May 5, 2015.

  1. ThatGuyFromTheWeb

    ThatGuyFromTheWeb

    Joined:
    Jan 23, 2014
    Posts:
    29
    Hi folks.

    I'm a unity noob and currently playing around with several possibilities for task based threading in Unity using C#. It seems, the used mono version is roughly equivalent to .NET 3.5. I think the TaskParallelLibrary (TPL) was introduced with .NET 4.0, but searching around i found a backport of the TPL for .NET 3.5 here.

    https://www.nuget.org/packages/TaskParallelLibrary/

    Did anybody already try to use this for a unity project? I have just taken a quick look and it seems if you included the provided System.Threading.dll in your project, it compiles with unity and you gain access to Parallel.For, Task<T> and so on.

    Well ... before invest more time in this, i'd like to hear if anybody already tried this and may point me to some caveats, showstoppers or licensing issues that may come along using this library.

    Thanks. :)
     
  2. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    I just wrote my own. I haven't commented very well, but it's pretty straight forward and easy to use.

    The manager class is a pseudo-singleton that automatically creates and attaches itself to a game object if one isn't placed in the editor. Pretty standard:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. namespace UnityExtensions {
    6.  
    7.     public class ThreadManager : MonoBehaviour {
    8.      
    9.         static ThreadManager _instance;
    10.         public static ThreadManager instance {
    11.             get {      
    12.  
    13.                 if (!_instance)
    14.                     CreateInstance();
    15.  
    16.                 return _instance;
    17.             }
    18.         }
    19.      
    20.         static void CreateInstance(GameObject gameObject = null) {
    21.  
    22.             if (!gameObject)
    23.                 _instance = (new GameObject ("Thread Manager", typeof(ThreadManager))).GetComponent<ThreadManager> ();
    24.             else
    25.                 _instance = gameObject.AddComponent<ThreadManager> ();
    26.  
    27.             EnsureSingleton();
    28.          
    29.         }
    30.      
    31.         static void EnsureSingleton() {
    32.          
    33.             var threadManagerInstances = GameObject.FindObjectsOfType<ThreadManager>();
    34.  
    35.             if (!_instance && threadManagerInstances.Length > 0)
    36.                 _instance = threadManagerInstances [0];
    37.  
    38.             if (threadManagerInstances.Length != 1) {
    39.                 Debug.LogError ("Only one instance of the Thread manager should ever exist. Removing extraneous.");
    40.              
    41.                 foreach (var instance in threadManagerInstances) {
    42.                     if (instance != ThreadManager.instance)
    43.                         Destroy (instance);
    44.                  
    45.                 }  
    46.             }
    47.  
    48.         }
    49.              
    50.         List<Thread> threads;
    51.         public void AddThread(Thread thread) {
    52.      
    53.             if (threads == null)
    54.                 threads = new List<Thread> ();
    55.  
    56.             if (!threads.Contains(thread))
    57.                 threads.Add(thread);
    58.         }
    59.  
    60.         public const int MaxRunningThreads = 7;
    61.  
    62.         int _runningThreads;
    63.         public int numRunningThreads {
    64.             get {
    65.                 return _runningThreads;
    66.             }
    67.         }
    68.  
    69.         public int numThreads {
    70.             get {
    71.                 return (threads == null) ? 0 :  threads.Count;
    72.             }
    73.         }
    74.      
    75.         void Awake() {
    76.      
    77.             EnsureSingleton();
    78.      
    79.         }
    80.  
    81.         void Update() {
    82.  
    83.             if (threads == null || threads.Count == 0 || threadsUpdating)
    84.                 return;
    85.  
    86.             StartCoroutine (UpdateThreads ());
    87.  
    88.         }
    89.      
    90.         bool threadsUpdating = false;
    91.         IEnumerator UpdateThreads() {
    92.  
    93.             threadsUpdating = true;
    94.  
    95.             _runningThreads = 0;
    96.  
    97.             var removeIndexes = new List<int> ();
    98.             for (int index = 0; index < threads.Count; index++) {
    99.  
    100.                 var thread = threads [index];
    101.  
    102.                 if (thread == null || thread.Expired) {
    103.                     removeIndexes.Add (index);
    104.                     continue;
    105.                 }
    106.  
    107.                 thread.Update ();
    108.                 _runningThreads += (thread.Running) ? 1 : 0;
    109.  
    110.                 if (ChunkTracker.AdvocateChunk ())
    111.                     yield return new WaitForEndOfFrame ();
    112.  
    113.             }
    114.  
    115.             for (int i = removeIndexes.Count - 1; i >= 0; i--)
    116.                 threads.RemoveAt (removeIndexes [i]);
    117.  
    118.  
    119.             threadsUpdating = false;
    120.  
    121.             yield break;
    122.  
    123.         }
    124.  
    125.     }
    126.  
    127. }
    The thread class is an abstract class that you derive. Implement it's abstract/virtual members in order to make a threadable task:

    Code (CSharp):
    1. using SysThread = System.Threading.Thread;
    2. using SysThreadStart = System.Threading.ThreadStart;
    3.  
    4. namespace UnityExtensions {
    5.  
    6.     public abstract class Thread {
    7.      
    8.         SysThread thread;
    9.      
    10.         bool start = false;
    11.         bool complete = false;
    12.         bool finished = false;
    13.  
    14.         public bool Expired {
    15.             get {
    16.                 return (!start && finished && !Running);
    17.             }
    18.         }
    19.  
    20.         string name;
    21.         public string Name {
    22.             get {
    23.                 return name;
    24.             }
    25.         }
    26.  
    27.         /// <summary>
    28.         /// Puts the thread in the manager queue if it isn't already, and queues it up to fire again if is. If it's already queued to fire again, nothing happens.
    29.         /// </summary>
    30.         public void Queue() {
    31.  
    32.             start = true;
    33.  
    34.             if (!Running) {
    35.  
    36.                 ThreadManager.instance.AddThread(this);
    37.  
    38.             }
    39.  
    40.         }
    41.  
    42.         public void Abort() {
    43.  
    44.             thread.Abort ();
    45.             complete = true;
    46.  
    47.         }
    48.      
    49.         /// <summary>
    50.         /// Update should be called by the ThreadManager.
    51.         /// </summary>
    52.         public void Update() {
    53.  
    54.             if (complete) {
    55.                 complete = false;
    56.                 finished = true;
    57.              
    58.                 OnComplete();
    59.              
    60.             }
    61.  
    62.             if (ThreadManager.instance.numRunningThreads >= ThreadManager.MaxRunningThreads)
    63.                 return;
    64.          
    65.             if (start && (thread == null || !thread.IsAlive)) {
    66.                 start = false;
    67.                 finished = false;
    68.  
    69.                 OnStart();
    70.  
    71.                 thread = new SysThread( new SysThreadStart(()=>{
    72.                     OnExecute();
    73.                     complete = true;
    74.                 }));
    75.                 thread.IsBackground = true;
    76.                 thread.Name = name;
    77.                 thread.Start ();
    78.              
    79.             }
    80.  
    81.         }
    82.  
    83.         public bool Running {
    84.  
    85.             get {
    86.                 return thread != null && thread.IsAlive;
    87.             }
    88.  
    89.         }
    90.      
    91.         /// <summary>
    92.         /// Called before the thread is executed. Use this to prepare data from the unity API for the thread to use.
    93.         /// </summary>
    94.         protected virtual void OnStart() {}
    95.      
    96.         /// <summary>
    97.         /// Called after the thread is completed. Use this to use output from the thread action on the API.
    98.         /// </summary>
    99.         protected virtual void OnComplete () {}
    100.      
    101.         /// <summary>
    102.         /// Everything that happens inside the thread. No calls to the Unity API may be made here.
    103.         /// </summary>
    104.         protected abstract void OnExecute ();
    105.  
    106.         public Thread(string name = "Background Thread") {
    107.  
    108.             this.name = name;
    109.  
    110.         }
    111.      
    112.     }
    113.  
    114. }
    115.  
    Example usage is as follows:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityExtensions;
    3.  
    4. public class Test : MonoBehaviour {
    5.      void Start() {
    6.           var counter = new Counter();
    7.           counter.Queue();
    8.      }
    9. }
    10.  
    11. public class Counter : Thread {
    12.  
    13.     float timeStamp;
    14.     int target;
    15.  
    16.     //OnStart is an optional method. We prepare everything that's going to happen inside the thread here.
    17.     //Remember, Unitys API is strictly forbidden from being called in any thread but the main thread, so if
    18.     //we need to use the API for anything, it has to be done here!
    19.     protected override void OnStart () {
    20.      
    21.         timeStamp = Time.time; //Can't call Time.time inside another thread!
    22.         int target = Random.Range (10, 200000000);
    23.      
    24.     }
    25.  
    26.     //Here is the magic method. This is what is going to happen on another thread.
    27.     protected override void OnExecute () {
    28.  
    29.         int i = 0;
    30.         while (i < target) //Ground breaking usefulness.
    31.             i++;
    32.      
    33.     }
    34.  
    35.     //Another optional method. Anything that requires the Unity API once the task is done can go here.
    36.     protected override void OnComplete ()
    37.     {
    38.  
    39.         float executionTime = Time.time - timeStamp;
    40.         float roundedSeconds = Mathf.Round (executionTime * 100f) / 100f;
    41.         Debug.Log ("It took " + roundedSeconds + " to count to " + target);
    42.  
    43.     }
    44.  
    45. }
    46.  
    I realize this isn't really an answer to your question, but you're free to use it if you find it useful.
     
    AnswerFinder likes this.
  3. ThatGuyFromTheWeb

    ThatGuyFromTheWeb

    Joined:
    Jan 23, 2014
    Posts:
    29
    Hi BenZed,

    thanks for the answer (and the offered code ;-) ) ... I should have mentioned in the first post, that i already have some kind of threadpool available and it seems to work fine in most cases.

    I'm simply trying to find something even more easy to use stuff. I also would like to include a library from MathNet.Numerics for faster processing of bigger float data arrays for terrain heightmaps and detaildata. Since they make heavy use of the Parallel namespace i'm trying to figure out, if i can get it to work.

    Coming to that now. It seems to work somehow with build target Standalone (Windows), but i don't know how it is with Mac or Linux. The webplayer doesn't work - as far as i read that's because of some security restrictions coming with sandboxed mode, that one can't work around. I also have no way to tell if it would work on mobile systems. Basically you seem to need the right to load and execute arbitrary methods in 3rd party dll-libraries.

    In case anybody wants to test, the steps seem to be something like:

    • Get the 3.5 Task Parallel Library ( for example here https://www.nuget.org/packages/TaskParallelLibrary/ )
    • Import the contained System.Threading.dll in your project
    • Make sure it is referenced by your unity project, when you open VisualStudio or Monodevelop
    • Set target framework to something with .Net 3.5 (Unity 3.5 .net subset may be sufficient?)
    • Write funny code
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Threading;
    4. using System.Threading.Tasks;
    5.  
    6. public class ThreadTest : MonoBehaviour {
    7.  
    8.   void Start () {
    9.  
    10.   // Fill 100.000.000 vectors using thread-local RNGs
    11.   var values = new Vector3[100000000];
    12.  
    13.   Parallel.For<System.Random>(
    14.       0,  values.Length,  // lower and upper loop bound
    15.       ( ) => new System.Random(), // init thread local var
    16.       ( i, loop, rnd ) =>  // loopindex, state and local var
    17.           {
    18.               values[i] = new Vector3(0, 0, i + (float) rnd.NextDouble());
    19.  
    20.              if ( i % 1000000 == 0 )
    21.               UnityEngine.Debug.Log("Thread-" + Thread.CurrentThread.ManagedThreadId + " worked hard! Currently i is " + i.ToString());
    22.  
    23.             return rnd;
    24.           },
    25.   ( x )  => {}  // a postprocessing action running after a thread finished
    26.   );
    27.   }
    28. }
    29.  
    30.  
     
    Last edited: May 6, 2015
  4. kmasonMobilityWare

    kmasonMobilityWare

    Joined:
    May 13, 2016
    Posts:
    1
    Lerzpftz, any update? did you have success with TPL in Unity on non-Windows platforms?
     
    Qbit86 likes this.
  5. TheProfessor

    TheProfessor

    Joined:
    Nov 2, 2014
    Posts:
    74
    What's "ChunkTracker"?
     
  6. benzsuankularb

    benzsuankularb

    Joined:
    Apr 10, 2013
    Posts:
    132
    Any progress ?