
.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.
13 November 2011