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+

Child Tasks

The thirteenth part of the Parallel Programming in .NET tutorial describes the use of attached child tasks. Like nested tasks, these are created within the scope of an existing parallel task. The difference is that child tasks are linked to their parents.

What is a Child Task?

In the previous article in this tutorial we considered detached child tasks, which are also known as nested tasks. These are parallel tasks that are created within the delegate of another parallel task. However, despite their nested nature, detached child tasks retain no connection to the task that created them. To link a child task to its parent you must used attached child tasks, which we will abbreviate to child tasks from now on.

A child task is created within the delegate of its parent in a similar manner to a nested task. The key difference is that the child task is linked to its parent in several ways. The link provides two key features automatically:

  • A parent task will not finish executing until all of its child tasks have completed, either normally or with exceptions. The parent essentially performs a Wait command for all of its children.
  • If a child task throws an exception that is otherwise unhandled, it is captured by the parent and rethrown. It is possible for many child tasks to throw an exception. All will be combined with any unhandled exception thrown by the parent directly in a single AggregateException.

NB: The above points do not give an exhaustive list of the differences between a nested task and a child task.

Creating Child Tasks

In this section we'll create some attached child tasks using similar examples to those from the "Nested Tasks" article. To simplify the references to key classes, add the following using directives:

using System.Threading;
using System.Threading.Tasks;

When you create a task within the scope of another's lambda expression, the task is nested by default. To link the child task to its parent you need to add a second parameter to its constructor. This allows you to define task creation options. To attach the task you should pass the value, TaskCreationOptions.AttachedToParent. This is shown in the sample code below, where the option is added to the constructors of the loadUserPermissionsTask and loadUserConfigurationTask objects, after the lambda expressions that define the tasks.

var loadUserDataTask = new Task(() =>
{
    Console.WriteLine("Loading User Data");
    Thread.Sleep(2000);
    string userID = "1234";
    Console.WriteLine("User ID loaded");

    var loadUserPermissionsTask = new Task(() =>
    {
        Console.WriteLine("Loading User Permissions for user {0}", userID);
        Thread.Sleep(2000);
        Console.WriteLine("User permissions loaded");
    }, TaskCreationOptions.AttachedToParent);
    loadUserPermissionsTask.Start();

    var loadUserConfigurationTask = new Task(() =>
    {
        Console.WriteLine("Loading User Configuration for user {0}", userID);
        Thread.Sleep(2000);
        Console.WriteLine("User configuration loaded");
    }, TaskCreationOptions.AttachedToParent);
    loadUserConfigurationTask.Start();

    Console.WriteLine("Parent task finished");
});

loadUserDataTask.Start();
loadUserDataTask.Wait();
loadUserDataTask.Dispose();

/* OUTPUT

Loading User Data
User ID loaded
Parent task finished
Loading User Permissions for user 1234
Loading User Configuration for user 1234
User configuration loaded
User permissions loaded

*/

When the above code is executed, the parent task launches both child tasks. Before the parent task completes, it waits for the two child tasks to finish. You can see this in the output. If the child tasks had not been waited for, their messages would not have been displayed, as the program would have completed before they had time to execute.

Handling Exceptions in Child Tasks

Unlike nested tasks, child tasks propagate exceptions through their parent task automatically. If you wish to handle a child task exception within the parent task, you can add a Wait method within the parent task, detect the exceptions and process them as you would within nested tasks. However, you can also elect not to handle the child task's exceptions. In this case, all child task exceptions are combined into an AggregateException, which is thrown by the parent task when it completes. If the parent task also throws an exception, this will also be included in the AggregateException's InnerExceptions property. You can catch the AggregateException by wrapping a Wait for the parent task in a try / catch block.

In the code below the child tasks both throw exceptions, as does the parent task. Note that the child tasks' Wait methods are never called directly but the error messages are still outputted by the main thread. As with nested tasks, the exceptions from child tasks are held in nested AggregateExceptions, so the exception is flattened before being iterated.

var loadUserDataTask = new Task(() =>
{
    Console.WriteLine("Loading User Data");
    Thread.Sleep(2000);
    string userID = "1234";
    Console.WriteLine("User ID loaded");

    var loadUserPermissionsTask = new Task(() =>
    {
        Console.WriteLine("Loading User Permissions for user {0}", userID);
        Thread.Sleep(2000);
        throw new Exception("User permissions could not be loaded");
    }, TaskCreationOptions.AttachedToParent);
    loadUserPermissionsTask.Start();

    var loadUserConfigurationTask = new Task(() =>
    {
        Console.WriteLine("Loading User Configuration for user {0}", userID);
        Thread.Sleep(2000);
        throw new Exception("User configuration could not be loaded");
    }, TaskCreationOptions.AttachedToParent);
    loadUserConfigurationTask.Start();

    throw new Exception("Parent task failed");
});

loadUserDataTask.Start();

try
{
    loadUserDataTask.Wait();
}
catch (AggregateException ex)
{
    foreach (var exception in ex.Flatten().InnerExceptions)
    {
        Console.WriteLine(exception.Message);
    }
}

loadUserDataTask.Dispose();

/* OUTPUT

Loading User Data
User ID loaded
Loading User Permissions for user 1234
Loading User Configuration for user 1234
Parent task failed
User permissions could not be loaded
User configuration could not be loaded

*/
13 November 2011