Executors Framework

Author: Magnus Wolffelt

Description
This is a small framework designed to assist in the usage of multiple threads in a C#/.Net program. Multi-threading is a very complex subject, and it's really hard to write bug-free multi-threaded code - hence the need for frameworks that make it a little easier.

The way this works is with the concepts of "Executors", "Callables" and "Futures". An executor is an object that consumes Callable objects (work tasks), and returns Future objects. These Future objects have a generic parameter which is the result type of the computation. The Future objects can also be polled for completion, or they can be requsted to return the result - which blocks the calling thread until the result has been computed.

While similar to the AsyncOperation provided by Unity, this concept is primarily inspired by the Java standard library, which features an extensive collection of implementations for this purpose.

Usage
To execute tasks, you need an executor:

Then you submit tasks:

And then you can either poll:

or... get the result directly (blocking call):

Note that any exception cast during the computation task, will be thrown when calling GetResult.

Also, the ExecutionManager helper component can take care of the polling, and do a delegate callback from the Unity thread, which is safe.

The MultiplyIntsTask looks like this:

This particular task is very fast and really not a good candidate for threaded processing. Consider this an illustrative example only.

Why?
(Skip this section if you're already convinced! ;))

The primary advantage over AsyncOperation and .Net Begin/EndInvoke is that tasks in this framework are executed by an explicitly specified executor, which means one can easily change the manner in which async tasks are executed. For example, simply exchange the SingleThreadExecutor instantiation for an ImmediateExecutor, and you are no longer using multi-threading at all, but your other code remains exactly the same as before.

If someone implemented a thread-pool executor with multiple threads processing submitted tasks, the interface would remain the same, and you could easily switch from no threading, to dual-threading, to pooled threading, with no modifications to existing code. This is really convenient in some cases where you are not sure of the best way, or you want it to be configurable in runtime.

What's wrong with Begin/EndInvoke?
Like stated above, Begin/EndInvoke does not provide a means for controlling the way tasks are executed. It accesses a global (VM scope even?) thread pool, to which task are submitted. So you can't easily switch between threaded and non-threaded approaches - not even in compile time. Executors Framework lets the user change execution style even in runtime, by just replacing the executor object. This may sound like a small detail, but for me it is important and has been very useful on several occasions.

Why ICallable interface, and not simply delegates?
This is a good question, and the framework might switch to delegates in the future. However, some concerns surfaced when delegates were tried briefly:
 * 1) Anonymous delegates can behave "oddly" when using outer variables, and invoked later. For example, it's not safe to use an iterator int value in an anonymous delegate object, unless the delegate is invoke immediately before the iterator variable is incremented.
 * 2) Sometimes you will want to, later, access and inspect the data passed to the execution task that finished, which is trivial if using classes implementing the ICallable interface, but more complex when using delegate objects.

Until I feel that these issues have been resolved in a satisfying way, the framework will stick to the ICallable interface, which makes it more apparent what is going on with variables and data.

Code
There are 10 files:
 * IExecutor.cs
 * ICallable.cs
 * Future.cs
 * ImmediateExecutor.cs
 * SingleThreadExecutor.cs
 * WorkItem.cs
 * ExecutionException.cs
 * ExecutorTester.cs (Unit tests, not required for usage)
 * ExecutionManager.cs (Unity helper component, not required for usage)
 * Example.cs (Helper component usage example, not required for usage)

Download all as one zipped unity package