Replacing the ThreadPool with Tasks, Continuations, and Futures
With .NET 4.0 a new version of the thread pool will become available. In addition to performance and load balancing enhancements, this new thread pool allows you to use Tasks. A Task is a lightweight unit of work similar to what you create when you use ThreadPool.QueueUserWorkItem. But unlike the anonymous work item, Task objects expose a powerful API. You can access this API by capturing the return value from calls to the
Task.StartNew function. This function, like those for threads, accepts a delegate or anonymous function defining what work is to be performed.
Just like full threads, tasks expose a method allowing the scheduling thread to wait for the task’s completion. The methods are Wait, WaitAny, and WaitAll, the latter two accepting a list of tasks. Waits can be absolute or can have a maximum timeout.
With waits, the waiting thread is blocked. When blocking isn’t a good option, but you still want to perform an action when the current task(s) are complete, you can use continuations. A continuation in this context is a operation that is scheduled to be performed when the task or set of tasks are completed. To schedule a follow-up task, you can call the ContinueWith, ContinueWithAll, or ContinueWithAny function and pass it a delegate. This function can be called multiple times, effectively creating a pipeline of operations.
Cancellation is one area that .NET does not currently support very well. There is no way to remove unneeded tasks from the current thread pool and thread aborts are considered to be very dangerous. With .NET 4, cancellation of tasks becomes a first class citizen. When a task that has not been started is cancelled, it is removed from the queue immediately. If the task has begun, cooperation with the operation is needed. The delegate that actually performs the task’s work needs to periodically check the IsCancellationRequested property and respond appropriately.
Tasks have another feature not seen before in .NET, it supports parent-child relationships. This allows complex tasks to be broken into smaller parts but still be seen as a single logical operation. This offers significant advantages for supporting things such as cancellation. When a parent task is cancelled, child tasks are optionally cancelled as well.
Futures are tasks that execute a function asynchronously. When the result of the function is needed, the Future checks to see if the function has completed. A value is returned if it is done processing, what happens if it is not done depends on the implementation.
For example, Reading the Value property will cause the thread to block until the task is complete. Like other tasks, you have access to the IsCompleted property and the Wait, ContinueWith, and Cancel methods. This added functionality makes .NET futures more powerful than the textbook description and arguably not a future at all. For this reason, among others, .NET 4.0 futures have been renamed Task<T> where T is the return type.
It should be noted that Tasks in .NET are not constrained to thread-safe operations. There is nothing preventing, say a future that calculates a derivative, from also changing a global variable. This means normal locking and object ownership rules still apply, though of course these can be minimized by exclusively using immutable objects.
Reminds me of the changes for Java 1.5
Tom Gilb & Kai Gilb Jan 26, 2015