Please note, this article refers to an old version of the TaskRunner which is not supported anymore. The latest version is here.

Every now and then it is necessary to run a series of tasks in a given order, for example to guarantee that some data has been downloaded before to continue the execution of the application. C# 4.0 offers powerful tools in order to perform these operations, like the Task Parallel Library, which seems very straightforward to use.

However Unity3D supports c# 3.5 only and beside the System.Threading routines, there are not easy way to accomplish the task.

All the Unity3D programmers know that Unity 3D exploits nicely the yield instruction to simulate multi-threading using coroutines (which are, of course, single threaded), but what are they exactly?

As explained in many tutorials, once an iterator block is met, the compiler creates a special switch case function that allows even complicated methods to be time-sliced; Something very similar to the various pseudo threading framework implementations that exist for single-threading environments (like in actionscript).

Time sliced techniques have been used for ages in the game industry, mostly to spread complicated routine execution over several rendering frames. So nothing new here, except the fact that c# is so smart to be able to create very sophisticated time-sliced routine on its own, so that the code stays very readable. Albeit, using yield for the execution of several tasks could get awkward.

This is why I decided to create a library that could accept, combine and run async tasks in parallel and serial. An example of the way it can be used is given by the following method:

As you can see, the code is pretty straightforward, there are just some notes to take in consideration:

  • TaskRunner is a Monobehaviour that exploits the StartRoutine function to register the task(list) to run. You must have one enabled in order to start the execution.
  • SerialTasks and ParallelTasks are two classes of the framework, they are quite self-explanatory
  • SerialTasks and ParallelTasks can execute both standard IEnumerable and ITask objects
  • ITask is a special interface added in order to manage special cases, but it is not mandatory to use.

The following is a naive example (for testing purposes) of an ITask object:

So what is the trick that let the sequential and parallel tasks be mixed together? The idea behind this code is to create a simple stack structure that recognize when a new IEnumerable (or IEnumerator) object is returned from a yield return. Nothing harder than:

In this way I can run a set of sequential tasks from parallel tasks and viceversa, but as you probably got the code is not limited just to these two cases, it actually can handle all the possible combinations deriving from the use of IEnumerable functions or objects.

I wish to thank my friend Amedeo Margarese who gave me the idea to write this code and Francesco Carucci who helped to write the unit tests for NunitLite

10
Leave a Reply

avatar
6 Comment threads
4 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
7 Comment authors
Sebastiano MandalàMichaelOlegVili VolčiniDanko Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
Caue C M Rego (@cawas)
Guest
Caue C M Rego (@cawas)

Please, I wish to see more examples. 😛

I’m trying to figure out how to use this… And I’m stuck in 2 points.

First, very basic, how should I implement the actual task?

Second, how can I serialize it within the task?

Thanks in advance, and great job!

Caue C M Rego (@cawas)
Guest
Caue C M Rego (@cawas)

Yes, that I can see in the current examples… But then, how would I implement the `IEnumerable`? 😀

I’ll eventually figure it out and I’m actually having some progress with good old trial and error, but since you asked about needing more examples I thought those would be nice additions.

Also, we can use it just like IEnumerators, can’t we? Make RunSerialTasksExecutedInParallel a IEnumerator (rather than void) and insert a `while (! parallelTasks1.isDone) yield return null;` in the end. Is there any problems in doing this?

Danko
Guest
Danko

If you are interested, eDriven has the TaskQueue class for synchronizing subsequent tasks (i.e. waiting for each task to end before handling the consequent one): https://github.com/dkozar/eDriven/blob/master/eDriven.Core/Tasks/TaskQueue.cs (and does it single threaded)

(seems we share the interest in this area :))

Vili Volčini
Guest
Vili Volčini

Hello, one question!

Are those parallel Tasks multi-threaded? Thanks for this script, I hope it will be usefull 🙂

I need to process multiple meshes at startup and I really want to use multi-threading for this.

Oleg
Guest
Oleg

courutines neither parallel nor async. They are synchronous, but concurrent. Learn the difference.

Michael
Guest
Michael

Hello Sebastiano ! Your Asyncronius Tasks is very good and helpfull ! SerialTaskCollection codestyle allow keep all sequence in one function. That make async logic clear. But some times we need to pass result from one async function to next, without using this result elsewhere. So I write args helper .. ( may be not elegant but do job ). Take a look. May be it will be usefull for You and Your library. http://www.codeshare.io/jufP5 Usage : public void Run() { mutual = new MutableArgs(); SerialTaskCollection st = new SerialTaskCollection(); st.Add(Print(1, mutual.Out)); st.Add(AsyncWaitAndCreateURL(mutual.In, mutual.Out)); st.Add(WWWTest(mutual.In)); StartCoroutine(st.GetEnumerator()); } IEnumerator Print(int i,… Read more »

Sebastiano Mandalà
Admin
Sebastiano Mandalà

Hi Michael, I do the same, but I don’t think you need to use Func (unless I misunderstood what you want to solve), I solved the same problem using simple references. Assuming we are talking about a single thread scenario, we can safely pass references as parameters of IEnumerator functions and they will work as well. I also implemented the “token” parameter to solve the same problem when ITask are used instead. I once wrote an elegant solution for a quite complicated scenario. I needed the serial tasks to branch according a parameter. It worked more or less like this:… Read more »