Присоединенные и отсоединенные дочерние задачи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. НетNo YesYes
Родительский объект распространяет исключения, созданные дочерними задачами.Parent propagates exceptions thrown by child tasks. НетNo YesYes
Состояние родительского объекта зависит от состояния дочернего объекта.Status of parent depends on status of child. НетNo YesYes

В большинстве случаев рекомендуется использовать отсоединенные дочерние задачи, поскольку их связи с другими задачами менее сложные.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.StartNewParent 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.

Чтобы запретить присоединение к родительской задаче дочерних задач, укажите параметр TaskCreationOptions.DenyChildAttach при создании родительской задачи Task или объекта Task<TResult>.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