Tareas secundarias asociadas y desasociadasAttached and Detached Child Tasks

Una tarea secundaria o tarea anidada es una instancia de System.Threading.Tasks.Task que se crea en el delegado de usuario de otra tarea, conocida como tarea primaria.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. Una tarea secundaria puede estar desasociada o asociada.A child task can be either detached or attached. Una tarea secundaria desasociada es una tarea que se ejecuta independientemente de su elemento principal.A detached child task is a task that executes independently of its parent. Una tarea secundaria asociada es una tarea anidada que se crea con la opción TaskCreationOptions.AttachedToParent y cuyo elemento primario no le prohíbe asociarse de forma explícita o predeterminada.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. Una tarea puede crear cualquier número de tareas secundarias asociadas y desasociadas, con la única limitación de los recursos del sistema.A task may create any number of attached and detached child tasks, limited only by system resources.

En la tabla siguiente se muestran las diferencias básicas entre los dos tipos de tareas secundarias.The following table lists the basic differences between the two kinds of child tasks.

CategoríaCategory Tareas secundarias desasociadasDetached child tasks Tareas secundarias asociadasAttached child tasks
La tarea primaria espera a que se completen las tareas secundarias.Parent waits for child tasks to complete. NoNo Yes
La tarea primaria propaga las excepciones que producen las tareas secundarias.Parent propagates exceptions thrown by child tasks. NoNo Yes
El estado de la tarea primaria depende del estado de la tarea secundaria.Status of parent depends on status of child. NoNo Yes

En la mayoría de los casos, se recomienda usar tareas secundarias desasociadas porque las relaciones con otras tareas son menos complejas.In most scenarios, we recommend that you use detached child tasks, because their relationships with other tasks are less complex. Esta es la razón por la que las tareas que se crean dentro de tareas primarias están desasociadas de forma predeterminada y es necesario especificar explícitamente la opción TaskCreationOptions.AttachedToParent para crear una tarea secundaria asociada.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.

Tareas secundarias desasociadasDetached child tasks

Aunque una tarea secundaria se crea mediante una tarea primaria, de forma predeterminada es independiente de la tarea primaria.Although a child task is created by a parent task, by default it is independent of the parent task. En el ejemplo siguiente se muestra una tarea primaria que crea una tarea secundaria simple.In the following example, a parent task creates one simple child task. Si se ejecuta varias veces el código de ejemplo, se observa que el resultado del ejemplo se diferencia del que se muestra, y también que el resultado puede cambiar cada vez que se ejecuta el código.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. Esto ocurre porque la tarea primaria y las tareas secundarias se ejecutan de forma independiente; la tarea secundaria es una tarea independiente.This occurs because the parent task and child tasks execute independently of each other; the child is a detached task. El ejemplo únicamente espera que se complete la tarea primaria; la tarea secundaria puede no ejecutarse o completarse antes de que finalice la aplicación de consola.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.

Si la tarea secundaria se representa mediante un objeto Task<TResult> en lugar de un objeto Task, puede garantizarse que la tarea primaria esperará a que la tarea secundaria se complete teniendo acceso a la propiedad Task<TResult>.Result de la tarea secundaria, aunque sea una tarea secundaria desasociada.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. La propiedad Result se bloquea hasta que se completa su tarea, como se muestra en el ejemplo siguiente.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
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

Tareas secundarias asociadasAttached child tasks

A diferencia de las tareas secundarias desasociadas, las tareas secundarias asociadas se sincronizan estrechamente con la tarea primaria.Unlike detached child tasks, attached child tasks are closely synchronized with the parent. En el ejemplo anterior, se puede cambiar la tarea secundaria desasociada a una tarea secundaria asociada mediante la opción TaskCreationOptions.AttachedToParent en la instrucción de creación de tareas, como se muestra en el ejemplo siguiente.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. En este código, la tarea secundaria asociada se completa antes que su tarea primaria.In this code, the attached child task completes before its parent. Como resultado, el resultado del ejemplo es igual cada vez que se ejecuta el código.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.

Puede usar tareas secundarias asociadas para crear gráficos con una estrecha sincronización de operaciones asincrónicas.You can use attached child tasks to create tightly synchronized graphs of asynchronous operations.

Sin embargo, una tarea secundaria se puede asociar a su elemento primario solo si su elemento primario no prohíbe las tareas secundarias asociadas.However, a child task can attach to its parent only if its parent does not prohibit attached child tasks. Las tareas primarias pueden evitar explícitamente que las tareas secundarias se asocien a ellas especificando la opción TaskCreationOptions.DenyChildAttach del constructor de clase de la tarea primaria o el método 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. Las tareas primarias impiden implícitamente que las tareas secundarias se asocien a ellas si se crean mediante una llamada al método Task.Run.Parent tasks implicitly prevent child tasks from attaching to them if they are created by calling the Task.Run method. Esto se ilustra en el siguiente ejemplo:The following example illustrates this. Es idéntico al ejemplo anterior, excepto en que la tarea primaria se crea mediante una llamada al método Task.Run(Action) en lugar de al método 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. Como la tarea secundaria no se puede asociar a su elemento primario, el resultado del ejemplo es impredecible.Because the child task is not able to attach to its parent, the output from the example is unpredictable. Como las opciones de creación de tareas predeterminadas para las sobrecargas de Task.Run incluyen TaskCreationOptions.DenyChildAttach, este ejemplo es funcionalmente equivalente al primer ejemplo de la sección "Tareas secundarias desasociadas".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.

Excepciones en tareas secundariasExceptions in child tasks

Si una tarea secundaria desasociadas inicia una excepción, esa excepción debe observarse o controlarse directamente en la tarea primaria como si se tratara de una tarea no anidada.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. Si una tarea secundaria asociada inicia una excepción, la excepción se propaga automáticamente a la tarea primaria y de nuevo al subproceso, que espera o intenta obtener acceso a la propiedad Task<TResult>.Result de la tarea.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. Por tanto, si se usan tareas secundarias asociadas, se pueden controlar todas las excepciones en un solo punto en la llamada a Task.Wait del subproceso que realiza la llamada.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. Para más información, consulte Control de excepciones.For more information, see Exception Handling.

Cancelación y tareas secundariasCancellation and child tasks

La cancelación de tareas es cooperativa.Task cancellation is cooperative. Es decir, para que se pueda cancelar, cada tarea secundaria asociada o desasociada debe supervisar el estado del token de cancelación.That is, to be cancelable, every attached or detached child task must monitor the status of the cancellation token. Si desea cancelar un elemento primario y todos sus elementos secundarios utilizando una solicitud de cancelación, debe pasar el mismo token como argumento a todas las tareas y proporcionar en cada tarea la lógica de respuesta a la solicitud en cada tarea.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. Para más información, vea Cancelación de tareas y Cómo: Cancelar una tarea y sus elementos secundarios.For more information, see Task Cancellation and How to: Cancel a Task and Its Children.

Cuando la tarea primaria se cancelaWhen the parent cancels

Si una tarea primaria se cancela antes de que se inicie su tarea secundaria, la tarea secundaria nunca se inicia.If a parent cancels itself before its child task is started, the child never starts. Si una tarea primaria se cancela después de que se ha iniciado su tarea secundaria, la tarea secundaria se ejecutará hasta completarse a menos que tenga su propia lógica de cancelación.If a parent cancels itself after its child task has already started, the child runs to completion unless it has its own cancellation logic. Para más información, vea Task Cancellation.For more information, see Task Cancellation.

Cuando una tarea secundaria desasociada se cancelaWhen a detached child task cancels

Si una tarea secundaria desasociada se cancela usando el mismo token que se pasó a la tarea primaria, y la tarea primaria no espera a la tarea secundaria, no se propagará ninguna excepción puesto que la excepción se trata cono una cancelación de cooperación benigna.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. Este comportamiento es igual que el de cualquier tarea de nivel superior.This behavior is the same as that of any top-level task.

Cuando se cancela una tarea secundaria asociadaWhen an attached child task cancels

Cuando una tarea secundaria asociada se cancela usando el mismo token que se pasó a su tarea primaria, se propaga una excepción TaskCanceledException al subproceso de unión dentro de 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. Se debe esperar a la tarea primaria para poder controlar todas las excepciones benignas además de todas las excepciones de error que se propagan de manera ascendente a través de un gráfico de tareas secundarias asociadas.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.

Para más información, consulte Control de excepciones.For more information, see Exception Handling.

Impedir que una tarea secundaria se adjunte a su tarea primaraPreventing a child task from attaching to its parent

Una excepción no controlada producida por una tarea secundaria se propaga a la tarea primaria.An unhandled exception that is thrown by a child task is propagated to the parent task. Puede usar este comportamiento para observar todas las excepciones de tareas secundarias desde una tarea raíz en lugar de recorrer un árbol de tareas.You can use this behavior to observe all child task exceptions from one root task instead of traversing a tree of tasks. Sin embargo, la propagación de excepciones puede dar problemas cuando una tarea primaria no cuenta con datos adjuntos de otro código.However, exception propagation can be problematic when a parent task does not expect attachment from other code. Por ejemplo, piense en una aplicación que llama a un componente de la biblioteca de terceros de un objeto Task.For example, consider an app that calls a third-party library component from a Task object. Si el componente de la biblioteca de terceros también crea un objeto Task y especifica TaskCreationOptions.AttachedToParent para asociarlo a la tarea primaria, las excepciones no controladas que aparecen en la tarea secundaria se propagan a la tarea primaria.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. Esto podría dar lugar a un comportamiento inesperado en la aplicación principal.This could lead to unexpected behavior in the main app.

Para evitar que una tarea secundaria se adjunte a su tarea primaria, especifique la opción TaskCreationOptions.DenyChildAttach cuando cree el objeto primario Task o 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. Cuando una tarea intenta asociarse a su elemento primario y el elemento primario especifica la opción TaskCreationOptions.DenyChildAttach, la tarea secundaria no podrá asociarse a un elemento primario y se ejecutará como si no se hubiera especificado la opción 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.

Puede que también desee evitar que una tarea secundaria se adjunte a su tarea primaria cuando la tarea secundaria no finaliza a tiempo.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. Dado que una tarea primaria no finaliza hasta que finalizan todas las tareas secundarias, una tarea secundaria que se ejecute durante mucho tiempo puede provocar que el rendimiento general de la aplicación sea mediocre.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. Para obtener un ejemplo en el que se muestra cómo mejorar el rendimiento de la aplicación evitando que una tarea se asocie a su tarea primaria, vea Procedimiento para evitar que una tarea secundaria se adjunte a su elemento primario.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.

Vea tambiénSee also