Any experience using the Task Parallel Library with unity?

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.

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 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. :slight_smile:

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:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace UnityExtensions {
  
    public class ThreadManager : MonoBehaviour {
      
        static ThreadManager _instance;
        public static ThreadManager instance {
            get {      

                if (!_instance)
                    CreateInstance();

                return _instance;
            }
        }
      
        static void CreateInstance(GameObject gameObject = null) {

            if (!gameObject)
                _instance = (new GameObject ("Thread Manager", typeof(ThreadManager))).GetComponent<ThreadManager> ();
            else
                _instance = gameObject.AddComponent<ThreadManager> ();

            EnsureSingleton();
         
        }
      
        static void EnsureSingleton() {
          
            var threadManagerInstances = GameObject.FindObjectsOfType<ThreadManager>();

            if (!_instance && threadManagerInstances.Length > 0)
                _instance = threadManagerInstances [0];

            if (threadManagerInstances.Length != 1) {
                Debug.LogError ("Only one instance of the Thread manager should ever exist. Removing extraneous.");
              
                foreach (var instance in threadManagerInstances) {
                    if (instance != ThreadManager.instance)
                        Destroy (instance);
                  
                }  
            }

        }
              
        List<Thread> threads;
        public void AddThread(Thread thread) {
      
            if (threads == null)
                threads = new List<Thread> ();

            if (!threads.Contains(thread))
                threads.Add(thread);
        }

        public const int MaxRunningThreads = 7;

        int _runningThreads;
        public int numRunningThreads {
            get {
                return _runningThreads;
            }
        }

        public int numThreads {
            get {
                return (threads == null) ? 0 :  threads.Count;
            }
        }
      
        void Awake() {
      
            EnsureSingleton();
      
        }

        void Update() {

            if (threads == null || threads.Count == 0 || threadsUpdating)
                return;

            StartCoroutine (UpdateThreads ());
  
        }
      
        bool threadsUpdating = false;
        IEnumerator UpdateThreads() {

            threadsUpdating = true;

            _runningThreads = 0;

            var removeIndexes = new List<int> ();
            for (int index = 0; index < threads.Count; index++) {

                var thread = threads [index];

                if (thread == null || thread.Expired) {
                    removeIndexes.Add (index);
                    continue;
                }

                thread.Update ();
                _runningThreads += (thread.Running) ? 1 : 0;

                if (ChunkTracker.AdvocateChunk ())
                    yield return new WaitForEndOfFrame ();

            }

            for (int i = removeIndexes.Count - 1; i >= 0; i--)
                threads.RemoveAt (removeIndexes [i]);


            threadsUpdating = false;

            yield break;

        }

    }

}

The thread class is an abstract class that you derive. Implement it’s abstract/virtual members in order to make a threadable task:

using SysThread = System.Threading.Thread;
using SysThreadStart = System.Threading.ThreadStart;

namespace UnityExtensions {
  
    public abstract class Thread {
      
        SysThread thread;
      
        bool start = false;
        bool complete = false;
        bool finished = false;

        public bool Expired {
            get {
                return (!start && finished && !Running);
            }
        }

        string name;
        public string Name {
            get {
                return name;
            }
        }

        /// <summary>
        /// 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.
        /// </summary>
        public void Queue() {

            start = true;

            if (!Running) {

                ThreadManager.instance.AddThread(this);

            }

        }

        public void Abort() {

            thread.Abort ();
            complete = true;

        }
      
        /// <summary>
        /// Update should be called by the ThreadManager.
        /// </summary>
        public void Update() {

            if (complete) {
                complete = false;
                finished = true;
              
                OnComplete();
              
            }

            if (ThreadManager.instance.numRunningThreads >= ThreadManager.MaxRunningThreads)
                return;
          
            if (start && (thread == null || !thread.IsAlive)) {
                start = false;
                finished = false;

                OnStart();

                thread = new SysThread( new SysThreadStart(()=>{
                    OnExecute();
                    complete = true;
                }));
                thread.IsBackground = true;
                thread.Name = name;
                thread.Start ();
              
            }

        }

        public bool Running {

            get {
                return thread != null && thread.IsAlive;
            }

        }
      
        /// <summary>
        /// Called before the thread is executed. Use this to prepare data from the unity API for the thread to use.
        /// </summary>
        protected virtual void OnStart() {}
      
        /// <summary>
        /// Called after the thread is completed. Use this to use output from the thread action on the API.
        /// </summary>
        protected virtual void OnComplete () {}
      
        /// <summary>
        /// Everything that happens inside the thread. No calls to the Unity API may be made here.
        /// </summary>
        protected abstract void OnExecute ();

        public Thread(string name = "Background Thread") {

            this.name = name;

        }
      
    }
  
}

Example usage is as follows:

using UnityEngine;
using UnityExtensions;

public class Test : MonoBehaviour {
     void Start() {
          var counter = new Counter();
          counter.Queue();
     }
}

public class Counter : Thread {
  
    float timeStamp;
    int target;
  
    //OnStart is an optional method. We prepare everything that's going to happen inside the thread here.
    //Remember, Unitys API is strictly forbidden from being called in any thread but the main thread, so if
    //we need to use the API for anything, it has to be done here!
    protected override void OnStart () {
      
        timeStamp = Time.time; //Can't call Time.time inside another thread!
        int target = Random.Range (10, 200000000);
      
    }

    //Here is the magic method. This is what is going to happen on another thread.
    protected override void OnExecute () {

        int i = 0;
        while (i < target) //Ground breaking usefulness.
            i++;
      
    }

    //Another optional method. Anything that requires the Unity API once the task is done can go here.
    protected override void OnComplete ()
    {

        float executionTime = Time.time - timeStamp;
        float roundedSeconds = Mathf.Round (executionTime * 100f) / 100f;
        Debug.Log ("It took " + roundedSeconds + " to count to " + target);

    }
  
}

I realize this isn’t really an answer to your question, but you’re free to use it if you find it useful.

1 Like

Hi BenZed,

thanks for the answer (and the offered code :wink: ) … 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

using UnityEngine;
using System.Threading;
using System.Threading.Tasks;

public class ThreadTest : MonoBehaviour {

  void Start () {

  // Fill 100.000.000 vectors using thread-local RNGs
  var values = new Vector3[100000000]; 

  Parallel.For<System.Random>(
      0,  values.Length,  // lower and upper loop bound
      ( ) => new System.Random(), // init thread local var
      ( i, loop, rnd ) =>  // loopindex, state and local var
          {
              values[i] = new Vector3(0, 0, i + (float) rnd.NextDouble());
 
             if ( i % 1000000 == 0 )
              UnityEngine.Debug.Log("Thread-" + Thread.CurrentThread.ManagedThreadId + " worked hard! Currently i is " + i.ToString()); 

            return rnd;
          }, 
  ( x )  => {}  // a postprocessing action running after a thread finished
  );
  }
}

Lerzpftz, any update? did you have success with TPL in Unity on non-Windows platforms?

1 Like

What’s “ChunkTracker”?

Any progress ?