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+

Continuation Tasks

The eleventh part of the Parallel Programming in .NET tutorial considers the use of continuation tasks. These are parallel tasks that start automatically when one or more other tasks complete, allowing a chain of dependent tasks to be executed correctly.

Multiple Continuations of a Single Antecedent

In more complex scenarios you may wish to create several tasks that are dependent upon a single antecedent completing but that can run in parallel to each other. You can achieve this by using the ContinueWith method of the antecedent more than once. As you may expect, once the antecedent has finished executing, all of its continuations become available for scheduling. If the machine running the software has the capacity, the continuations can run in parallel.

The sample below demonstrates this by adding to the earlier example. Here the loadUserDataTask is used to obtain a user ID. Two continuation tasks, loadUserPermissionsTask and loadUserConfigurationTask, use the retrieved value in order to load the correct permissions and configuration options for the user.

In this case the Task.WaitAll method is used to wait for both continuations to complete before showing the final message.

var loadUserDataTask = new Task<string>(() =>
{
    Console.WriteLine("Loading User Data");
    Thread.Sleep(2000);
    Console.WriteLine("User data loaded");
    return "1234";
});

var loadUserPermissionsTask = loadUserDataTask.ContinueWith(t =>
{
    Console.WriteLine("Loading User Permissions for user {0}", t.Result);
    Thread.Sleep(2000);
    Console.WriteLine("User permissions loaded");
});

var loadUserConfigurationTask = loadUserDataTask.ContinueWith(t =>
{
    Console.WriteLine("Loading User Configuration for user {0}", t.Result);
    Thread.Sleep(2000);
    Console.WriteLine("User configuration loaded");
});

loadUserDataTask.Start();
Task.WaitAll(loadUserPermissionsTask, loadUserConfigurationTask);

Console.WriteLine("CRM Application Loaded");

loadUserDataTask.Dispose();
loadUserPermissionsTask.Dispose();
loadUserConfigurationTask.Dispose();

/* OUTPUT

Loading User Data
User data loaded
Loading User Permissions for user 1234
Loading User Configuration for user 1234
User configuration loaded
User permissions loaded
CRM Application Loaded

*/

Creating Continuations with Multiple Antecedents

Another complex scenario is where you have a continuation task that depends upon the completion of several antecedents. This too is possible with the TPL, though not using the ContinueWith method. Instead, you can use the static Task.Factory.ContinueWhenAll method. This accepts an array of Task objects as its first parameter, all of which must complete before the new task can be scheduled. The lambda expression defining the continuation is the second argument. The array of antecedents is passed to the delegate, which is of the type, Action<Task[]>. The continuation is returned.

The next example expands upon the previous one. After the loadUserPermissionsTask and loadUserConfigurationTask tasks have completed, finalTask will be scheduled. The final task shows the results of the antecedent tasks, after extracting them from the array.

var loadUserDataTask = new Task<string>(() =>
{
    Console.WriteLine("Loading User Data");
    Thread.Sleep(2000);
    Console.WriteLine("User data loaded");
    return "1234";
});

var loadUserPermissionsTask = loadUserDataTask.ContinueWith(t =>
{
    Console.WriteLine("Loading User Permissions for user {0}", t.Result);
    Thread.Sleep(2000);
    Console.WriteLine("User permissions loaded");
    return "Admin";
});

var loadUserConfigurationTask = loadUserDataTask.ContinueWith(t =>
{
    Console.WriteLine("Loading User Configuration for user {0}", t.Result);
    Thread.Sleep(2000);
    Console.WriteLine("User configuration loaded");
    return "Rich UI";
});

var dependencies = new Task[] { loadUserPermissionsTask, loadUserConfigurationTask };
var finalTask = Task.Factory.ContinueWhenAll(dependencies, t =>
{
    Console.WriteLine("\nCRM Application Loaded");
    Console.WriteLine("User permissions : {0}", ((Task<string>)t[0]).Result);
    Console.WriteLine("User experience  : {0}", ((Task<string>)t[1]).Result);
});

loadUserDataTask.Start();
finalTask.Wait();

loadUserDataTask.Dispose();
loadUserPermissionsTask.Dispose();
loadUserConfigurationTask.Dispose();
finalTask.Dispose();

/* OUTPUT

Loading User Data
User data loaded
Loading User Permissions for user 1234
Loading User Configuration for user 1234
User permissions loaded
User configuration loaded

CRM Application Loaded
User permissions : Admin
User experience  : Rich UI

*/
24 October 2011