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+

Nested Tasks

The twelfth part of the Parallel Programming in .NET tutorial looks at the ability for tasks to be nested. A nested task is one that is created and executed within the delegate of another. Nested tasks are not linked to their parent tasks.

What is a Nested Task?

When you are developing parallel applications it is common to launch new tasks from within the delegate of an existing task. Tasks may be nested in this manner to many levels deep. The inner tasks are known as child tasks, of which there are two types. The first type is the detached child task, which is the focus of this article. These tasks are also known as nested tasks. The other type of child task is the attached child task, generally known simply as child tasks. We will see these in the next instalment of the tutorial. As the two types of child task have similar names, I will refer to detached child tasks as nested tasks and attached child tasks as child tasks for the rest of the tutorial.

When you create nested tasks there is no link between a nested task and its parent. Nested tasks are completely independent, reporting a separate status and throwing their own exceptions. The scheduling of parent and nested tasks are also separate; it is possible for parent tasks to complete before its nested tasks have commenced.

The code below shows a parent task with two nested tasks. You can see that the nested tasks are created, and started, within the delegate of the outer task. When executed the parent task will probably complete before the nested tasks have even started. This shows that the scheduling of the tasks is not linked. The Console.ReadLine method is included to give the nested tasks a chance to finish and their messages to be outputted. We cannot wait for the nested tasks outside of the parent task as the loadUserPremissionTask and loadUserConfigurationTask variables are out of scope at this point.

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");
    });
    loadUserPermissionsTask.Start();

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

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

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

Console.ReadLine();

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

Exceptions in Nested Tasks

As previously stated, the exceptions thrown by a task and its nested tasks are handled separately. An exception thrown by a nested task will not be automatically propagated by its parent. This means that if you do not wait for nested tasks to complete, using the Wait method or similar, these exceptions may not be handled and could be lost altogether.

We can demonstrate with the following sample. Here the nested tasks are modified versions of those in the first example. This time they both throw exceptions. The parent task also throws an exception and it is this one alone that is caught in the try / catch block surrounding the Wait statement. As no method is called to wait for the nested tasks, their exceptions remain unhandled. This is why we see only one inner exception in the caught AggregateException.

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");
    });
    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");
    });
    loadUserConfigurationTask.Start();

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

loadUserDataTask.Start();

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

loadUserDataTask.Dispose();

Console.ReadLine();

/*
            
Loading User Data
User ID loaded
Loading User Permissions for user 1234
Loading User Configuration for user 1234
Parent task failed
             
*/
4 November 2011