Вложенные и дочерние задачи

Вложенные задача — это просто экземпляр задачи Task, создаваемый в пользовательском делегате другой задачи. Дочерняя задача — это вложенная задача, создаваемая с помощью параметра AttachedToParent. Задача может создать любое количество дочерних и вложенных задач, ограничиваясь только системными ресурсами. В следующем примере показана родительская задача, создающая одну простую вложенную задачу.

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.
 */

Вложенные дочерние задачи и отсоединенные вложенные задачи

Наиболее важным моментом при сравнении дочерних и вложенных задач является то, что вложенные задачи по существу не зависят от родительской или внешней задачи, тогда как вложенные дочерние задачи очень близко синхронизируются с родительской задачей. Если изменить оператор создания задач так, чтобы использовался параметр AttachedToParent, как показано в следующем примере,

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);

будут получены следующие выходные данные.

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

Для создания тесно синхронизированных графиков асинхронных операций можно использовать вложенные дочерние задачи. Однако в большинстве сценариев рекомендуется использовать вложенные задачи, поскольку отношения с другими задачами менее сложные. Именно поэтому задачи, созданные внутри других задач, являются вложенными по умолчанию, и необходимо явно указать параметр AttachedToParent для создания дочерней задачи.

В следующей таблице перечислены основные различия между двумя видами дочерних задач.

Категория

Вложенные задачи

Вложенные дочерние задачи

Внешняя задача (родительская) ожидает завершения внутренних задач.

Нет

Да

Родительская задача распространяет исключения, созданные дочерними задачами (внутренними задачами).

Нет

Да

Состояние родительской задачи (внешней задачи) зависит от состояния дочерней задачи (внутренней задачи).

Нет

Да

В отсоединенных сценариях, в которых вложенной задачей является Task<TResult>, можно по-прежнему принудительно указать родительской задаче ожидать дочернюю задачу, обратившись к свойству Result вложенной задачи. Свойство Result блокируется до завершения задачи.

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.
 */

Исключения во вложенных и дочерних задачах

Если вложенная задача создает исключение, оно должно рассматриваться или обрабатываться непосредственно во внешней задаче так же, как и в случае любой невложенной задачи. Если вложенная дочерняя задача создает исключение, оно автоматически распространяется в родительскую задачу и назад в поток, который ожидает или пытается получить доступ к свойству Result задачи. Таким образом, используя вложенные дочерние задачи, можно обрабатывать все исключения в одной точке, а именно вызове Wait в вызывающем потоке. Дополнительные сведения см. в разделе Обработка исключений (библиотека параллельных задач).

Отмена и дочерние задачи

Помните, что отмена задачи выполняется совместно. Следовательно, чтобы иметь возможность отмены, каждая вложенная или отсоединенная дочерняя задача должна отслеживать состояние токена отмены. Если необходимо отменить родительскую задачу и все ее дочерние задачи с помощью одного запроса отмены, следует передать один и тот же токен в качестве аргумента во все задачи и предоставить в каждую задачу логику для ответа на запрос. Дополнительные сведения см. в разделах Отмена задач и Практическое руководство. Отмена задачи и ее дочерних элементов.

Отмена родительской задачи

Если родительская задача отменяет саму себя до запуска дочерней задачи, дочерняя (вложенная) задача, очевидно, никогда не запустится. Если родительская задача отменяет саму себя после запуска дочерней или вложенной задачи, вложенная (дочерняя) задача будет завершена, если она не имеет собственной логики отмены. Дополнительные сведения см. в разделе Отмена задач.

Отмена вложенной задачи

Если отсоединенная дочерняя задача отменяет саму себя с помощью того же токена, который был передан в задачу, и родительская задача не ожидает дочерней задачи, исключение не распространяется, поскольку оно считается благоприятной совместной отменой. Данное поведение аналогично поведению любой задачи верхнего уровня.

Отмена дочерней задачи

Если вложенная дочерняя задача отменяет саму себя, используя тот же токен, который был передан в задачу, исключение TaskCanceledException распространяется в присоединяемый поток внутри AggregateException. Очень важно ожидать родительскую задачу, чтобы можно было обработать все благоприятные исключения в дополнение ко всем исключениям с ошибкой, которые распространяются вверх по графику вложенных дочерних задач.

Дополнительные сведения см. в разделе Обработка исключений (библиотека параллельных задач).

См. также

Основные понятия

Параллельное программирование в .NET Framework

Параллелизм данных (библиотека параллельных задач)