已附加和已分离的子任务Attached and Detached Child Tasks

子任务(或嵌套任务)是在另一个任务(称为“父任务”)的用户委托中创建的 System.Threading.Tasks.Task 实例。A child task (or nested task) is a System.Threading.Tasks.Task instance that is created in the user delegate of another task, which is known as the parent task. 可以分离或附加子任务。A child task can be either detached or attached. 分离的子任务是独立于父级而执行的任务。A detached child task is a task that executes independently of its parent. 附加的子任务是使用 TaskCreationOptions.AttachedToParent 选项创建的嵌套任务,父级不显式或默认禁止附加任务。An attached child task is a nested task that is created with the TaskCreationOptions.AttachedToParent option whose parent does not explicitly or by default prohibit it from being attached. 一个任务可以创建任意数量的附加和分离子任务,这仅受系统资源限制。A task may create any number of attached and detached child tasks, limited only by system resources.

下表列出了两种子任务之间的基本差异。The following table lists the basic differences between the two kinds of child tasks.

类别Category 分离子任务Detached child tasks 附加子任务Attached child tasks
父级将等待子任务完成。Parent waits for child tasks to complete. NoNo Yes
父级将传播由子任务引发的异常。Parent propagates exceptions thrown by child tasks. NoNo Yes
父级的状态取决于子级的状态。Status of parent depends on status of child. NoNo Yes

在大多数情况下,我们建议你使用分离子任务,因为它们与其他任务之间的关系不太复杂。In most scenarios, we recommend that you use detached child tasks, because their relationships with other tasks are less complex. 这就是父任务内创建的任务会默认分离的原因,并且必须显式指定 TaskCreationOptions.AttachedToParent 选项来创建附加子任务。That is why tasks created inside parent tasks are detached by default, and you must explicitly specify the TaskCreationOptions.AttachedToParent option to create an attached child task.

分离子任务Detached child tasks

尽管子任务是由父任务创建的,但在默认情况下,它独立于父任务。Although a child task is created by a parent task, by default it is independent of the parent task. 在以下示例中,父任务创建了一个简单的子任务。In the following example, a parent task creates one simple child task. 如果多次运行该示例的代码,你可能会注意到该示例的输出与所演示的输出不同,并且该输出可能在每次运行代码时,会发生更改。If you run the example code multiple times, you may notice that the output from the example differs from that shown, and also that the output may change each time you run the code. 发生这种情况的原因是父任务和子任务彼此独立执行;子任务是一个分离任务。This occurs because the parent task and child tasks execute independently of each other; the child is a detached task. 该示例仅等待父任务完成,并且子任务在控制台应用终止之前,可能无法执行或完成。The example waits only for the parent task to complete, and the child task may not execute or complete before the console app terminates.

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

public class Example
{
   public static void Main()
   {
      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.");
   }
}
// The example produces output like the following:
//        Outer task executing.
//        Nested task starting.
//        Outer has completed.
//        Nested task completing.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      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
End Module
' The example produces output like the following:
'   Outer task executing.
'   Nested task starting.
'   Outer task has completed.
'   Nested task completing.

如果该子任务由 Task<TResult> 对象,而不是 Task 对象表示,则你可以通过访问子任务的 Task<TResult>.Result 属性,确保父任务将等待子任务完成,即使该子任务是一个分离子任务。If the child task is represented by a Task<TResult> object rather than a Task object, you can ensure that the parent task will wait for the child to complete by accessing the Task<TResult>.Result property of the child even if it is a detached child task. Result 属性在其任务完成前会进行阻止,如以下示例所示。The Result property blocks until its task completes, as the following example shows.

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

class Example
{
   static void Main()
   {
      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);
   }
}
// The example displays the following output:
//       Outer task executing.
//       Nested task starting.
//       Nested task completing.
//       Outer has returned 42.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      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
End Module      
' The example displays the following output:
'       Outer task executing.
'       Nested task starting.
'       Detached task completing.
'       Outer has returned 42

附加子任务Attached child tasks

不同于分离子任务,附加子任务与父任务紧密同步。Unlike detached child tasks, attached child tasks are closely synchronized with the parent. 可以通过使用任务创建语句中的 TaskCreationOptions.AttachedToParent 选项,将之前示例中的分离子任务更改为附加子任务,如以下示例中所示。You can change the detached child task in the previous example to an attached child task by using the TaskCreationOptions.AttachedToParent option in the task creation statement, as shown in the following example. 在此代码中,附加子任务会在父任务之前完成。In this code, the attached child task completes before its parent. 因此,每次运行代码时,该示例的输出都是相同的。As a result, the output from the example is the same each time you run the code.

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

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
            Console.WriteLine("Parent task executing.");
            var child = Task.Factory.StartNew(() => {
                  Console.WriteLine("Attached child starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Attached child completing.");
            }, TaskCreationOptions.AttachedToParent);
      });
      parent.Wait();
      Console.WriteLine("Parent has completed.");
   }
}
// The example displays the following output:
//       Parent task executing.
//       Attached child starting.
//       Attached child completing.
//       Parent has completed.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim parent = Task.Factory.StartNew(Sub()
                                            Console.WriteLine("Parent task executing")
                                            Dim child = Task.Factory.StartNew(Sub()
                                                                                 Console.WriteLine("Attached child starting.")
                                                                                 Thread.SpinWait(5000000)
                                                                                 Console.WriteLine("Attached child completing.")
                                                                              End Sub, TaskCreationOptions.AttachedToParent)
                                         End Sub)
      parent.Wait()
      Console.WriteLine("Parent has completed.")
   End Sub
End Module
' The example displays the following output:
'       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, a child task can attach to its parent only if its parent does not prohibit attached child tasks. 通过在父任务类构造函数中指定 TaskCreationOptions.DenyChildAttach选项或 TaskFactory.StartNew 方法,父任务可以显式阻止子任务附加到其中。Parent tasks can explicitly prevent child tasks from attaching to them by specifying the TaskCreationOptions.DenyChildAttach option in the parent task's class constructor or the TaskFactory.StartNew method. 如果父任务是通过调用 Task.Run 方法而创建的,则可以隐式阻止子任务附加到其中。Parent tasks implicitly prevent child tasks from attaching to them if they are created by calling the Task.Run method. 下面的示例阐释了这一点。The following example illustrates this. 这与上述示例相同,除了该父任务是通过调用 Task.Run(Action) 方法,而不是 TaskFactory.StartNew(Action) 方法创建的。It is identical to the previous example, except that the parent task is created by calling the Task.Run(Action) method rather than the TaskFactory.StartNew(Action) method. 因为子任务不能附加到其父任务,则该示例的输出是不可预知的。Because the child task is not able to attach to its parent, the output from the example is unpredictable. 因为 Task.Run 重载的默认任务创建选项包括 TaskCreationOptions.DenyChildAttach,所以本示例在功能上等效于“分离子任务”部分中的第一个示例。Because the default task creation options for the Task.Run overloads include TaskCreationOptions.DenyChildAttach, this example is functionally equivalent to the first example in the "Detached child tasks" section.

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

public class Example
{
   public static void Main()
   {
      var parent = Task.Run(() => {
            Console.WriteLine("Parent task executing.");
            var child = Task.Factory.StartNew(() => {
                  Console.WriteLine("Child starting.");
                  Thread.SpinWait(5000000);
                  Console.WriteLine("Child completing.");
            }, TaskCreationOptions.AttachedToParent);
      });
      parent.Wait();
      Console.WriteLine("Parent has completed.");
   }
}
// The example displays output like the following:
//       Parent task executing
//       Parent has completed.
//       Attached child starting.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim parent = Task.Run(Sub()
                               Console.WriteLine("Parent task executing")
                               Dim child = Task.Factory.StartNew(Sub()
                                              Console.WriteLine("Attached child starting.")
                                              Thread.SpinWait(5000000)
                                              Console.WriteLine("Attached child completing.")
                                           End Sub, TaskCreationOptions.AttachedToParent)
                            End Sub)
      parent.Wait()
      Console.WriteLine("Parent has completed.")
   End Sub
End Module
' The example displays output like the following:
'       Parent task executing
'       Parent has completed.
'       Attached child starting.

子任务中的异常Exceptions in child tasks

如果分离子任务引发了异常,则该异常必须直接在父任务中进行观察和处理,正如任何非嵌套任务一样。If a detached child task throws an exception, that exception must be observed or handled directly in the parent task just as with any non-nested task. 如果附加子任务引发了异常,则该异常会自动传播到父任务,并返回到等待或尝试访问任务的 Task<TResult>.Result 属性的线程。If an attached child task throws an exception, the exception is automatically propagated to the parent task and back to the thread that waits or tries to access the task's Task<TResult>.Result property. 因此,通过使用附加子任务,可以一次性处理调用线程上对 Task.Wait 的调用中的所有异常。Therefore, by using attached child tasks, you can handle all exceptions at just one point in the call to Task.Wait on the calling thread. 有关详细信息,请参阅异常处理For more information, see Exception Handling.

取消和子任务Cancellation and child tasks

任务取消需要彼此协作。Task cancellation is cooperative. 也就是说,若要取消任务,则每个附加或分离的子任务必须监视取消标记的状态。That is, 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, you pass the same token as an argument to all tasks and provide in each task the logic to respond to the request in each task. 有关详细信息,请参阅任务取消如何:取消任务及其子级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 its child task is started, the child never starts. 如果父任务在其子任务已开始后取消了自身,则子任务将完成运行,除非它自己具有取消逻辑。If a parent cancels itself after its child task has already started, the child runs to completion unless it has its own cancellation logic. 有关详细信息,请参阅任务取消For more information, see Task Cancellation.

当分离子任务取消时When a detached child task cancels

如果分离子任务使用传递到父任务的相同标记取消自身,且父任务不会等待子任务,则不会传播异常,因为该异常将被视为良性协作取消。If a detached child task cancels itself by using the same token that was passed to the parent, and the parent does not wait for the child task, 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 an attached child task cancels

当附加子任务使用传递到其父任务的相同标记取消自身时,TaskCanceledException 将传播到 AggregateException 中的联接线程。When an attached child task cancels itself by using the same token that was passed to its parent task, a TaskCanceledException is propagated to the joining thread inside an AggregateException. 必须等待父任务,以便你除了所有通过附加子任务的图形传播的错误异常之外,还可以处理所有良性异常。You must wait for 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.

阻止子任务附加到其父任务Preventing a child task from attaching to its parent

由子任务引发的未经处理的异常将传播到父任务中。An unhandled exception that is thrown by a child task is propagated to the parent task. 可以使用此行为,从一个根任务而无需遍历任务树来观察所有子任务异常。You can use this behavior to observe all child task exceptions from one root task instead of traversing a tree of tasks. 但是,当父任务不需要其他代码的附件时,异常传播可能会产生问题。However, exception propagation can be problematic when a parent task does not expect attachment from other code. 例如,设想下从 Task 对象调用第三方库组件的应用。For example, consider an app that calls a third-party library component from a Task object. 如果第三方库组件也创建一个 Task 对象,并指定 TaskCreationOptions.AttachedToParent 以将其附加到父任务中,则子任务中出现的任何未经处理的异常将会传播到父任务。If the third-party library component also creates a Task object and specifies TaskCreationOptions.AttachedToParent to attach it to the parent task, any unhandled exceptions that occur in the child task propagate to the parent. 这可能会导致主应用中出现意外行为。This could lead to unexpected behavior in the main app.

若要防止子任务附加到其父任务,请在创建父任务 TaskTask<TResult> 对象时,指定 TaskCreationOptions.DenyChildAttach 选项。To prevent a child task from attaching to its parent task, specify the TaskCreationOptions.DenyChildAttach option when you create the parent Task or Task<TResult> object. 当某项任务尝试附加到其父任务,且其父任务指定了 TaskCreationOptions.DenyChildAttach 选项时,则子任务将不能附加到父任务,并且将像未指定 TaskCreationOptions.AttachedToParent 选项一样进行执行。When a task tries to attach to its parent and the parent specifies the TaskCreationOptions.DenyChildAttach option, the child task will not be able to attach to a parent and will execute just as if the TaskCreationOptions.AttachedToParent option was not specified.

可能还想要防止子任务在没有及时完成时附加到其父任务。You might also want to prevent a child task from attaching to its parent when the child task does not finish in a timely manner. 因为父任务只有在所有子任务完成后才会完成,所以长时间运行的子任务会使整个应用执行得非常缓慢。Because a parent task does not finish until all child tasks finish, a long-running child task can cause the overall app to perform poorly. 有关演示如何通过防止子任务附加到父任务来提升应用性能的示例,请参阅如何:防止子任务附加到父任务For an example that shows how to improve app performance by preventing a task from attaching to its parent task, see How to: Prevent a Child Task from Attaching to its Parent.

请参阅See also