BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Parallel and Asynchronous
.NET 4.0+

Creating Parallel Tasks with TaskFactory

The Task Parallel Library provides a number of ways in which parallel tasks can be instantiated. This article describes the use of the TaskFactory class, which uses the factory method design pattern to generate and start tasks with a single method call.

TaskFactory Class

In the Parallel Programming in .NET tutorial I described some of the key operations that can be performed using the Task Parallel Library (TPL). This included the creation and execution of parallel tasks where the Task objects where instantiated using their constructors. The TPL provides an alternative way to create and execute tasks in the TaskFactory class. Amongst other functionality, this class uses the Factory Method design pattern to generate new parallel tasks and schedule them for execution with a single method call.

The TaskFactory class is found in the System.Threading.Tasks namespace so add the following using directive to simplify its use:

using System.Threading.Tasks;

Task.Factory

It is sometimes necessary to create a new TaskFactory instance using one of its constructors. However, for most scenarios it is simpler to obtain a TaskFactory using the static Factory property of the Task class. This property returns a TaskFactory with standard settings, as if you had instantiated it using the default constructor. Unlike when using the constructor, the Factory property returns the same TaskFactory each time, preserving resources.

The following code obtains a TaskFactory instance using the Factory property. Usually you will not need to write such code as it makes sense to use the Task.Factory property directly each time.

TaskFactory factory = Task.Factory;

Starting Tasks

To create and start a new task with the TaskFactory you use the StartNew method. Using StartNew is more efficient than creating and starting tasks manually. When you start a task using its Start method, the TPL applies synchronisation to the process. This prevents two separate threads from attempting to start the same task at the same time. As the TaskFactory's StartNew method combines the two actions, no synchronisation is necessary.

There are many overloaded versions of the StartNew method to allow similar control to when instantiating and starting tasks separately. For our first example we'll use a version that has one parameter, which accepts an Action delegate. This delegate contains the code to be executed in the parallel process. Note that the method returns the generated task, allowing us to wait for it to complete.

var myTask = Task.Factory.StartNew(() => Console.WriteLine("Hello, world!"));
myTask.Wait();

You can also start a task that returns a result by passing a Func delegate to the StartNew method:

var myTask = Task.Factory.StartNew(() => { return "Hello, world!"; });
Console.WriteLine(myTask.Result);

Other overloaded versions allow you to pass cancellation tokens, state objects and task creation options, as well as allowing you to set the scheduler to be used. We won't create samples for all of these overloads here.

StartNew Limitation

When using the Task object returned by the StartNew method there is an important limitation. Although this value is in scope for the delegate of the task, you should not use it there. For example, consider the following code:

Task initialTask = null;

initialTask = Task.Factory.StartNew(() =>
{
    initialTask.ContinueWith(t => { Console.WriteLine("Continuation"); });
    Console.WriteLine("Initial");
});

On the surface this code looks OK. A task is generated by the StartNew method and is scheduled for execution. Within the initial task, a continuation is scheduled to be executed when the first task completes. If you execute the above code, you will probably see the results that were intended. Unfortunately the code hides a subtle bug that could cause intermittent errors.

When the StartNew method is executed, it is possible that the task scheduler starts executing the delegate before the task instance is returned. If the task manages to reach the line that sets the continuation before the initialTask variable has been set, attempting to create the continuation will throw a null reference exception. If you do need to use the task object within the delegate for any reason, you should not use the StartNew method. Instead, instantiate the task and start it separately.

3 May 2012