Dela via


Nested Tasks and Child Tasks

A nested task is just a Task instance that is created in the user delegate of another task. A child task is a nested task that is created with the AttachedToParent option. A task may create any number of child and/or nested tasks, limited only by system resources. The following example shows a parent task that creates one simple nested task.

Shared Sub SimpleNestedTask()
    Dim parent = Task.Factory.StartNew(Sub()
                                           Console.WriteLine("Outer task executing.")
                                           Dim child = Task.Factory.StartNew(Sub()
                                                                                 Console.WriteLine("Nested task starting.")
                                                                                 Thread.SpinWait(500000)
                                                                                 Console.WriteLine("Nested task completing.")
                                                                             End Sub)
                                       End Sub)
    parent.Wait()
    Console.WriteLine("Outer task has completed.")


End Sub

' Sample output:
'   Outer task executing.
'   Nested task starting.
'   Outer task has completed.
'   Nested task completing.
static void SimpleNestedTask()
{
    var parent = Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Outer task executing.");

        var child = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("Nested task starting.");
            Thread.SpinWait(500000);
            Console.WriteLine("Nested task completing.");
        });
    });

    parent.Wait();
    Console.WriteLine("Outer has completed.");
}

/* Sample output:
        Outer task executing.
        Nested task starting.
        Outer has completed.
        Nested task completing.
 */

Attached Child Tasks Versus Detached Nested Tasks

The most important point with respect to child vs. nested tasks is that nested tasks are essentially independent from the parent or outer task, whereas attached child tasks are very closely synchronized with the parent. If you change the task creation statement to use the AttachedToParent option, as shown in the following example,

Dim child = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Attached child starting.")
                                      Thread.SpinWait(5000000)
                                      Console.WriteLine("Attached child completing.")
                                  End Sub, TaskCreationOptions.AttachedToParent)
var child = Task.Factory.StartNew((t) =>
{

    Console.WriteLine("Attached child starting.");
    Thread.SpinWait(5000000);
    Console.WriteLine("Attached child completing.");
},                TaskCreationOptions.AttachedToParent);

the following output would be produced.

' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.
Parent task executing.
Attached child starting.
Attached child completing.
Parent has completed.

You can use attached child tasks to create tightly-synchronized graphs of asynchronous operations. However, in most scenarios, we recommend that you use nested tasks because the relationships with other tasks are less complex. That is why tasks created inside other tasks are nested by default, and you must explicitly specify the AttachedToParent option to create a child task.

The following table lists the basic differences between the two kinds of child tasks.

Category

Nested Tasks

Attached Child Tasks

Outer task (parent) waits for inner tasks to complete.

No

Yes

Parent propagates exceptions thrown by children (inner tasks).

No

Yes

Status of parent (outer task) dependent on status of child (inner task).

No

Yes

In detached scenarios where the nested task is a Task<TResult>, you can still force the parent to wait for a child by accessing the Result property of the nested task. The Result property blocks until its task completes.

Shared Sub WaitForSimpleNestedTask()
    Dim parent = Task(Of Integer).Factory.StartNew(Function()
                                                       Console.WriteLine("Outer task executing.")
                                                       Dim child = Task(Of Integer).Factory.StartNew(Function()
                                                                                                         Console.WriteLine("Nested task starting.")
                                                                                                         Thread.SpinWait(5000000)
                                                                                                         Console.WriteLine("Nested task completing.")
                                                                                                         Return 42
                                                                                                     End Function)
                                                       Return child.Result


                                                   End Function)
    Console.WriteLine("Outer has returned {0}", parent.Result)
End Sub
'Sample output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
static void WaitForSimpleNestedTask()
{
    var outer = Task<int>.Factory.StartNew(() =>
    {
        Console.WriteLine("Outer task executing.");

        var nested = Task<int>.Factory.StartNew(() =>
        {
            Console.WriteLine("Nested task starting.");
            Thread.SpinWait(5000000);
            Console.WriteLine("Nested task completing.");
            return 42;
        });

        // Parent will wait for this detached child.
        return nested.Result;
    });

    Console.WriteLine("Outer has returned {0}.", outer.Result);
}

/* Sample output:
    Outer task executing.
    Nested task starting.
    Nested task completing.
    Outer has returned 42.
 */

Exceptions in Nested and Child Tasks

If a nested task throws an exception, it must be observed or handled directly in the outer task just as with any non-nested task. If an attached child throws an exception, the exception is automatically propagated to the parent task and back to the thread that waits or attempts to access the task's Result property. Therefore, by using attached child tasks, you can handle all exceptions at just one point, namely the call to Wait on the calling thread. For more information, see Exception Handling (Task Parallel Library).

Cancellation and Child Tasks

Remember that task cancellation is cooperative. Therefore, to be "cancelable," every attached or detached child task must monitor the status of the cancellation token. If you want to cancel a parent and all its children by using one cancellation request, then pass the same token as an argument to all tasks and provide in each task the logic to respond to the request. For more information, see Task Cancellation and How to: Cancel a Task and Its Children.

When the Parent Cancels

If a parent cancels itself before a child is started, the child (nested) task will obviously never start. If a parent cancels itself after a child or nested task has already started, the nested (child) task will run to completion unless it has its own cancellation logic. For more information, see Task Cancellation.

When a Nested Task Cancels

If a detached child task cancels itself by using the same token that was passed to the task, and the parent does not wait on the child, then no exception is propagated because the exception is treated as benign cooperation cancellation. This behavior is the same as that of any top-level task.

When a Child Task Cancels

When an attached child task cancels itself by using the same token that was passed to the task, a TaskCanceledException is propagated to the joining thread inside an AggregateException. It is very important to wait on the parent task so that you can handle all benign exceptions in addition to all faulting exceptions that are propagated up through a graph of attached child tasks.

For more information, see Exception Handling (Task Parallel Library).

See Also

Other Resources

Parallel Programming in the .NET Framework

Data Parallelism (Task Parallel Library)