Encadenar tareas mediante tareas de continuaciónChaining Tasks by Using Continuation Tasks

En la programación asincrónica, es habitual que una operación asincrónica, al finalizar, invoque una segunda operación y le pase los datos.In asynchronous programming, it is common for one asynchronous operation, on completion, to invoke a second operation and pass data to it. Tradicionalmente, las continuaciones se han realizado mediante métodos de devolución de llamada.Traditionally, continuations have been done by using callback methods. En la biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas), se proporciona la misma funcionalidad mediante tareas de continuación.In the Task Parallel Library, the same functionality is provided by continuation tasks. Una tarea de continuación (también conocida simplemente como una continuación) es una tarea asincrónica invocada por otra tarea, conocida como el antecedente, cuando esta finaliza.A continuation task (also known just as a continuation) is an asynchronous task that is invoked by another task, which is known as the antecedent, when the antecedent finishes.

A pesar de que las continuaciones son relativamente fáciles de usar, resultan eficaces y flexibles.Continuations are relatively easy to use, but are nevertheless powerful and flexible. Por ejemplo, se puede:For example, you can:

  • Pasar datos del antecedente a la continuación.Pass data from the antecedent to the continuation.

  • Especificar las condiciones precisas en las que se invoca o no se invoca la continuación.Specify the precise conditions under which the continuation will be invoked or not invoked.

  • Cancelar una continuación antes de que se inicie o de forma cooperativa mientras se ejecuta.Cancel a continuation either before it starts or cooperatively as it is running.

  • Proporcionar sugerencias sobre cómo debería programarse la continuación.Provide hints about how the continuation should be scheduled.

  • Invocar varias continuaciones desde el mismo antecedente.Invoke multiple continuations from the same antecedent.

  • Invocar una continuación cuando todos o alguno de los antecedentes finalicen.Invoke one continuation when all or any one of multiple antecedents complete.

  • Encadenar las continuaciones una tras otra hasta cualquier longitud arbitraria.Chain continuations one after another to any arbitrary length.

  • Usar una continuación para controlar las excepciones producidas por el antecedente.Use a continuation to handle exceptions thrown by the antecedent.

Sobre las continuacionesAbout continuations

Una continuación es una tarea que se crea en el estado WaitingForActivationA continuation is a task that is created in the WaitingForActivation state. y que se activa automáticamente cuando su tarea (o tareas) antecedente finaliza.It is activated automatically when its antecedent task or tasks complete. Llamar a Task.Start en una continuación en código de usuario produce una excepción System.InvalidOperationException .Calling Task.Start on a continuation in user code throws an System.InvalidOperationException exception.

Una continuación es en sí misma un Task y no bloquea el subproceso en el que se inicia.A continuation is itself a Task and does not block the thread on which it is started. Para un bloqueo, llame al método Task.Wait hasta que finalice la tarea de continuación.Call the Task.Wait method to block until the continuation task finishes.

Crear una continuación para un antecedente únicoCreating a continuation for a single antecedent

Se puede crear una continuación que se ejecute una vez completado su antecedente mediante una llamada al método Task.ContinueWith .You create a continuation that executes when its antecedent has completed by calling the Task.ContinueWith method. En el ejemplo siguiente se muestra el patrón básico (para mayor claridad, se omite el control de excepciones).The following example shows the basic pattern (for clarity, exception handling is omitted). En él se ejecuta una tarea antecedente, taskA, que devuelve un objeto DayOfWeek que indica el nombre del día actual de la semana.It executes an antecedent task, taskA, that returns a DayOfWeek object that indicates the name of the current day of the week. Cuando finaliza el antecedente, este se pasa a la tarea de continuación, continuation, y se muestra una cadena que incluye su resultado.When the antecedent completes, the continuation task, continuation, is passed the antecedent and displays a string that includes its result.

Nota

En los ejemplos de C# de este artículo se usa el modificador async en el método Main.The C# samples in this article make use of the async modifier on the Main method. Esa característica está disponible en C# 7.1 y versiones posteriores.That feature is available in C# 7.1 and later. En las versiones anteriores se genera CS5001 al compilar este código de ejemplo.Previous versions generate CS5001 when compiling this sample code. Tendrá que establecer la versión del lenguaje en C# 7.1 o posterior.You'll need to set the language version to C# 7.1 or newer. Puede obtener información sobre cómo configurar la versión del lenguaje en el artículo sobre configuración de la versión del lenguaje.You can learn how to configure the language version in the article on configure language version.

using System;
using System.Threading.Tasks;

public class Example
{
   public static async Task Main()
   {
      // Execute the antecedent.
      Task<DayOfWeek> taskA = Task.Run( () => DateTime.Today.DayOfWeek );

      // Execute the continuation when the antecedent finishes.
      await taskA.ContinueWith( antecedent => Console.WriteLine("Today is {0}.", antecedent.Result) );
   }
}
// The example displays output like the following output:
//       Today is Monday.
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      ' Execute the antecedent.
      Dim taskA As Task(Of DayOfWeek) = Task.Run(Function() DateTime.Today.DayOfWeek )

      ' Execute the continuation when the antecedent finishes.
      Dim continuation As Task = taskA.ContinueWith(Sub(antecedent)
                                                       Console.WriteLine("Today is {0}.", antecedent.Result)
                                                    End Sub)
      continuation.Wait()
   End Sub
End Module
' The example displays output like the following output:
'       Today is Monday.

Crear una continuación para varios antecedentesCreating a continuation for multiple antecedents

También puede crear una continuación que se ejecutará cuando se haya completado una tarea o un grupo de tareas.You can also create a continuation that will run when any or all of a group of tasks has completed. Para ejecutar una continuación cuando se hayan completado todas las tareas antecedentes, llame al método estáticoShared ( Task.WhenAll en Visual Basic) o al método de instancia TaskFactory.ContinueWhenAll .To execute a continuation when all antecedent tasks have completed, you call the static (Shared in Visual Basic) Task.WhenAll method or the instance TaskFactory.ContinueWhenAll method. Para ejecutar una continuación cuando cualquiera de las tareas antecedentes se haya completado, llame al método estáticoShared ( Task.WhenAny en Visual Basic) o al método de instancia TaskFactory.ContinueWhenAny .To execute a continuation when any of the antecedent tasks has completed, you call the static (Shared in Visual Basic) Task.WhenAny method or the instance TaskFactory.ContinueWhenAny method.

Tenga en cuenta que las llamadas a las sobrecargas Task.WhenAll y Task.WhenAny no bloquean el subproceso que realiza la llamada.Note that calls to the Task.WhenAll and Task.WhenAny overloads do not block the calling thread. Sin embargo, se suele llamar a todos menos a los métodos Task.WhenAll(IEnumerable<Task>) y Task.WhenAll(Task[]) para recuperar la propiedad Task<TResult>.Result devuelta, que bloquea el subproceso que realiza la llamada.However, you typically call all but the Task.WhenAll(IEnumerable<Task>) and Task.WhenAll(Task[]) methods to retrieve the returned Task<TResult>.Result property, which does block the calling thread.

En el ejemplo siguiente se llama al método Task.WhenAll(IEnumerable<Task>) para crear una tarea de continuación que refleje los resultados de sus diez tareas antecedentes.The following example calls the Task.WhenAll(IEnumerable<Task>) method to create a continuation task that reflects the results of its 10 antecedent tasks. Cada tarea antecedente eleva al cuadrado un valor de índice que varía entre uno y diez.Each antecedent task squares an index value that ranges from one to 10. Si los antecedentes se completan correctamente (su propiedad Task.Status es TaskStatus.RanToCompletion), la propiedad Task<TResult>.Result de la continuación es una matriz de los valores Task<TResult>.Result devueltos por cada antecedente.If the antecedents complete successfully (their Task.Status property is TaskStatus.RanToCompletion), the Task<TResult>.Result property of the continuation is an array of the Task<TResult>.Result values returned by each antecedent. En el ejemplo se suman para calcular el total de los cuadrados de todos los números entre uno y diez.The example adds them to compute the sum of squares for all numbers between one and 10.

using System.Collections.Generic;
using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      List<Task<int>> tasks = new List<Task<int>>();
      for (int ctr = 1; ctr <= 10; ctr++) {
         int baseValue = ctr;
         tasks.Add(Task.Factory.StartNew( (b) => { int i = (int) b;
                                                   return i * i; }, baseValue));
      }
      var continuation = Task.WhenAll(tasks);

      long sum = 0;
      for (int ctr = 0; ctr <= continuation.Result.Length - 1; ctr++) {
         Console.Write("{0} {1} ", continuation.Result[ctr],
                       ctr == continuation.Result.Length - 1 ? "=" : "+");
         sum += continuation.Result[ctr];
      }
      Console.WriteLine(sum);
   }
}
// The example displays the following output:
//    1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385
Imports System.Collections.Generic
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim tasks As New List(Of Task(Of Integer))()
      For ctr As Integer = 1 To 10
         Dim baseValue As Integer = ctr
         tasks.Add(Task.Factory.StartNew( Function(b)
                                             Dim i As Integer = CInt(b)
                                             Return i * i
                                          End Function, baseValue))
      Next
      Dim continuation = Task.WhenAll(tasks)

      Dim sum As Long = 0
      For ctr As Integer = 0 To continuation.Result.Length - 1
         Console.Write("{0} {1} ", continuation.Result(ctr),
                       If (ctr = continuation.Result.Length - 1, "=", "+"))
         sum += continuation.Result(ctr)
      Next
      Console.WriteLine(sum)
   End Sub
End Module
' The example displays the following output:
'       1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385

Opciones de continuaciónContinuation options

Cuando se crea una continuación de tarea única, se puede usar una sobrecarga ContinueWith que toma un valor de enumeración System.Threading.Tasks.TaskContinuationOptions para especificar las condiciones en las que se inicia la continuación.When you create a single-task continuation, you can use a ContinueWith overload that takes a System.Threading.Tasks.TaskContinuationOptions enumeration value to specify the conditions under which the continuation starts. Por ejemplo, se puede especificar que la continuación únicamente se ejecute si el antecedente se completa correctamente, o solo si se completa en un estado de error.For example, you can specify that the continuation is to run only if the antecedent completes successfully, or only if it completes in a faulted state. Si la condición no es true cuando el antecedente está listo para invocar la continuación, la continuación realiza la transición directamente al estado TaskStatus.Canceled y, por tanto, no se puede iniciar.If the condition is not true when the antecedent is ready to invoke the continuation, the continuation transitions directly to the TaskStatus.Canceled state and subsequently cannot be started.

Hay distintos métodos de continuación de varias tareas, como las sobrecargas del método TaskFactory.ContinueWhenAll , que también incluyen un parámetro System.Threading.Tasks.TaskContinuationOptions .A number of multi-task continuation methods, such as overloads of the TaskFactory.ContinueWhenAll method, also include a System.Threading.Tasks.TaskContinuationOptions parameter. Sin embargo, solo es válido un subconjunto de todos los miembros de enumeración de System.Threading.Tasks.TaskContinuationOptions .Only a subset of all System.Threading.Tasks.TaskContinuationOptions enumeration members are valid, however. Se pueden especificar valores System.Threading.Tasks.TaskContinuationOptions que tengan equivalentes en la enumeración System.Threading.Tasks.TaskCreationOptions , como TaskContinuationOptions.AttachedToParent, TaskContinuationOptions.LongRunningy TaskContinuationOptions.PreferFairness.You can specify System.Threading.Tasks.TaskContinuationOptions values that have counterparts in the System.Threading.Tasks.TaskCreationOptions enumeration, such as TaskContinuationOptions.AttachedToParent, TaskContinuationOptions.LongRunning, and TaskContinuationOptions.PreferFairness. Si se especifica cualquiera de las opciones NotOn o OnlyOn con una continuación de varias tareas, se producirá una excepción ArgumentOutOfRangeException en tiempo de ejecución.If you specify any of the NotOn or OnlyOn options with a multi-task continuation, an ArgumentOutOfRangeException exception will be thrown at run time.

Para obtener más información sobre las opciones de continuación de tarea, consulte el tema TaskContinuationOptions .For more information on task continuation options, see the TaskContinuationOptions topic.

Pasar datos a una continuaciónPassing Data to a Continuation

El método Task.ContinueWith pasa una referencia al antecedente al delegado de usuario de la continuación como argumento.The Task.ContinueWith method passes a reference to the antecedent to the user delegate of the continuation as an argument. Si el antecedente es un objeto System.Threading.Tasks.Task<TResult> y la tarea se ejecutó hasta que se completó, la continuación puede luego tener acceso a la propiedad Task<TResult>.Result de la tarea.If the antecedent is a System.Threading.Tasks.Task<TResult> object, and the task ran until it was completed, then the continuation can access the Task<TResult>.Result property of the task.

La propiedad Task<TResult>.Result se bloquea hasta que se completa la tarea.The Task<TResult>.Result property blocks until the task has completed. Sin embargo, si la tarea se canceló o produjo errores y se intenta tener acceso a la propiedad Result , se producirá una excepción AggregateException .However, if the task was canceled or faulted, attempting to access the Result property throws an AggregateException exception. Puede evitar este problema con la opción OnlyOnRanToCompletion , tal como se muestra en el ejemplo siguiente.You can avoid this problem by using the OnlyOnRanToCompletion option, as shown in the following example.

using System;
using System.Threading.Tasks;

public class Example
{
   public static async Task Main()
   {
      var t = Task.Run( () => { DateTime dat = DateTime.Now;
                                if (dat == DateTime.MinValue)
                                   throw new ArgumentException("The clock is not working.");
                                   
                                if (dat.Hour > 17)
                                   return "evening";
                                else if (dat.Hour > 12)
                                   return "afternoon";
                                else
                                   return "morning"; });
      await t.ContinueWith( (antecedent) => { Console.WriteLine("Good {0}!",
                                                                  antecedent.Result);
                                                Console.WriteLine("And how are you this fine {0}?",
                                                                  antecedent.Result); },
                              TaskContinuationOptions.OnlyOnRanToCompletion);
   }
}
// The example displays output like the following:
//       Good afternoon!
//       And how are you this fine afternoon?
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim t = Task.Run( Function()
                           Dim dat As DateTime = DateTime.Now
                           If dat = DateTime.MinValue Then
                              Throw New ArgumentException("The clock is not working.")
                           End If
                           
                           If dat.Hour > 17 Then
                              Return "evening"
                           Else If dat.Hour > 12 Then
                              Return "afternoon"
                           Else
                              Return "morning"
                           End If
                        End Function)
      Dim c = t.ContinueWith( Sub(antecedent)
                                 Console.WriteLine("Good {0}!",
                                                   antecedent.Result)
                                 Console.WriteLine("And how are you this fine {0}?",
                                                   antecedent.Result)
                              End Sub, TaskContinuationOptions.OnlyOnRanToCompletion)
      c.Wait()
   End Sub
End Module
' The example displays output like the following:
'       Good afternoon!
'       And how are you this fine afternoon?

Si desea que la continuación se ejecute aunque el antecedente no finalice correctamente, debe protegerse de la excepción.If you want the continuation to run even if the antecedent did not run to successful completion, you must guard against the exception. Un modo de hacerlo es probar la propiedad Task.Status del antecedente y solo intentar el acceso a la propiedad Result si el estado no es Faulted ni Canceled.One approach is to test the Task.Status property of the antecedent, and only attempt to access the Result property if the status is not Faulted or Canceled. También puede examinar la propiedad Exception del antecedente.You can also examine the Exception property of the antecedent. Para más información, consulte Control de excepciones.For more information, see Exception Handling. En el ejemplo siguiente se modifica el ejemplo anterior para tener acceso a la propiedad Task<TResult>.Result del antecedente, pero solo si su estado es TaskStatus.RanToCompletion.The following example modifies the previous example to access antecedent's Task<TResult>.Result property only if its status is TaskStatus.RanToCompletion.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var t = Task.Run( () => { DateTime dat = DateTime.Now;
                                if (dat == DateTime.MinValue)
                                   throw new ArgumentException("The clock is not working.");
                                   
                                if (dat.Hour > 17)
                                   return "evening";
                                else if (dat.Hour > 12)
                                   return "afternoon";
                                else
                                   return "morning"; });
      var c = t.ContinueWith( (antecedent) => { if (t.Status == TaskStatus.RanToCompletion) {
                                                   Console.WriteLine("Good {0}!",
                                                                     antecedent.Result);
                                                   Console.WriteLine("And how are you this fine {0}?",
                                                                  antecedent.Result);
                                                }
                                                else if (t.Status == TaskStatus.Faulted) {
                                                   Console.WriteLine(t.Exception.GetBaseException().Message);
                                                }} );
   }
}
// The example displays output like the following:
//       Good afternoon!
//       And how are you this fine afternoon?
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim t = Task.Run( Function()
                           Dim dat As DateTime = DateTime.Now
                           If dat = DateTime.MinValue Then
                              Throw New ArgumentException("The clock is not working.")
                           End If
                           
                           If dat.Hour > 17 Then
                              Return "evening"
                           Else If dat.Hour > 12 Then
                              Return "afternoon"
                           Else
                              Return "morning"
                           End If
                        End Function)
      Dim c = t.ContinueWith( Sub(antecedent)
                                 If t.Status = TaskStatus.RanToCompletion Then
                                    Console.WriteLine("Good {0}!",
                                                      antecedent.Result)
                                    Console.WriteLine("And how are you this fine {0}?",
                                                      antecedent.Result)
                                 Else If t.Status = TaskStatus.Faulted Then
                                    Console.WriteLine(t.Exception.GetBaseException().Message)
                                 End If
                              End Sub)
   End Sub
End Module
' The example displays output like the following:
'       Good afternoon!
'       And how are you this fine afternoon?

Cancelar una continuaciónCanceling a Continuation

La propiedad Task.Status de una continuación se establece en TaskStatus.Canceled en las situaciones siguientes:The Task.Status property of a continuation is set to TaskStatus.Canceled in the following situations:

Si una tarea y su continuación representan dos partes de la misma operación lógica, se puede pasar el mismo token de cancelación a ambas tareas, tal como se muestra en el ejemplo siguiente.If a task and its continuation represent two parts of the same logical operation, you can pass the same cancellation token to both tasks, as shown in the following example. Consta de un antecedente que genera una lista de enteros divisibles por 33 y que pasa a la continuación.It consists of an antecedent that generates a list of integers that are divisible by 33, which it passes to the continuation. La continuación a su vez muestra la lista.The continuation in turn displays the list. El antecedente y la continuación se ponen en pausa periódicamente durante intervalos aleatorios.Both the antecedent and the continuation pause regularly for random intervals. Además, un objeto System.Threading.Timer se usa para ejecutar el método Elapsed después de un intervalo de tiempo de espera de cinco segundos.In addition, a System.Threading.Timer object is used to execute the Elapsed method after a five-second timeout interval. Este ejemplo llama al método CancellationTokenSource.Cancel, que hace que la tarea que se ejecuta actualmente llame al método CancellationToken.ThrowIfCancellationRequested.This example calls the CancellationTokenSource.Cancel method, which causes the currently executing task to call the CancellationToken.ThrowIfCancellationRequested method. Que el método CancellationTokenSource.Cancel se invoque cuando se está ejecutando el antecedente o su continuación depende de la duración de las pausas generadas de forma aleatoria.Whether the CancellationTokenSource.Cancel method is called when the antecedent or its continuation is executing depends on the duration of the randomly generated pauses. Si se cancela el antecedente, la continuación no se iniciará.If the antecedent is canceled, the continuation will not start. Si no se cancela el antecedente, el token aún puede usarse para cancelar la continuación.If the antecedent is not canceled, the token can still be used to cancel the continuation.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Random rnd = new Random();
      var cts = new CancellationTokenSource();
      CancellationToken token = cts.Token;
      Timer timer = new Timer(Elapsed, cts, 5000, Timeout.Infinite);

      var t = Task.Run( () => { List<int> product33 = new List<int>();
                                for (int ctr = 1; ctr < Int16.MaxValue; ctr++) {
                                   if (token.IsCancellationRequested) {
                                      Console.WriteLine("\nCancellation requested in antecedent...\n");
                                      token.ThrowIfCancellationRequested();
                                   }
                                   if (ctr % 2000 == 0) {
                                      int delay = rnd.Next(16,501);
                                      Thread.Sleep(delay);
                                   }

                                   if (ctr % 33 == 0)
                                      product33.Add(ctr);
                                }
                                return product33.ToArray();
                              }, token);

      Task continuation = t.ContinueWith(antecedent => { Console.WriteLine("Multiples of 33:\n");
                                                         var arr = antecedent.Result;
                                                         for (int ctr = 0; ctr < arr.Length; ctr++)
                                                         {
                                                            if (token.IsCancellationRequested) {
                                                               Console.WriteLine("\nCancellation requested in continuation...\n");
                                                               token.ThrowIfCancellationRequested();
                                                            }

                                                            if (ctr % 100 == 0) {
                                                               int delay = rnd.Next(16,251);
                                                               Thread.Sleep(delay);
                                                            }
                                                            Console.Write("{0:N0}{1}", arr[ctr],
                                                                          ctr != arr.Length - 1 ? ", " : "");
                                                            if (Console.CursorLeft >= 74)
                                                               Console.WriteLine();
                                                         }
                                                         Console.WriteLine();
                                                       } , token);

      try {
          continuation.Wait();
      }
      catch (AggregateException e) {
         foreach (Exception ie in e.InnerExceptions)
            Console.WriteLine("{0}: {1}", ie.GetType().Name,
                              ie.Message);
      }
      finally {
         cts.Dispose();
      }

      Console.WriteLine("\nAntecedent Status: {0}", t.Status);
      Console.WriteLine("Continuation Status: {0}", continuation.Status);
  }

   private static void Elapsed(object state)
   {
      CancellationTokenSource cts = state as CancellationTokenSource;
      if (cts == null) return;

      cts.Cancel();
      Console.WriteLine("\nCancellation request issued...\n");
   }
}
// The example displays the following output:
//    Multiples of 33:
//
//    33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
//    561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
//    1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
//    1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
//    1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
//    2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
//    2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
//    2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
//    3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
//    3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
//    3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
//    4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
//    4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
//    5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
//    5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
//    5,775, 5,808, 5,841, 5,874, 5,907, 5,940, 5,973, 6,006, 6,039, 6,072, 6,105,
//    6,138, 6,171, 6,204, 6,237, 6,270, 6,303, 6,336, 6,369, 6,402, 6,435, 6,468,
//    6,501, 6,534, 6,567, 6,600, 6,633, 6,666, 6,699, 6,732, 6,765, 6,798, 6,831,
//    6,864, 6,897, 6,930, 6,963, 6,996, 7,029, 7,062, 7,095, 7,128, 7,161, 7,194,
//    7,227, 7,260, 7,293, 7,326, 7,359, 7,392, 7,425, 7,458, 7,491, 7,524, 7,557,
//    7,590, 7,623, 7,656, 7,689, 7,722, 7,755, 7,788, 7,821, 7,854, 7,887, 7,920,
//    7,953, 7,986, 8,019, 8,052, 8,085, 8,118, 8,151, 8,184, 8,217, 8,250, 8,283,
//    8,316, 8,349, 8,382, 8,415, 8,448, 8,481, 8,514, 8,547, 8,580, 8,613, 8,646,
//    8,679, 8,712, 8,745, 8,778, 8,811, 8,844, 8,877, 8,910, 8,943, 8,976, 9,009,
//    9,042, 9,075, 9,108, 9,141, 9,174, 9,207, 9,240, 9,273, 9,306, 9,339, 9,372,
//    9,405, 9,438, 9,471, 9,504, 9,537, 9,570, 9,603, 9,636, 9,669, 9,702, 9,735,
//    9,768, 9,801, 9,834, 9,867, 9,900, 9,933, 9,966, 9,999, 10,032, 10,065, 10,098,
//    10,131, 10,164, 10,197, 10,230, 10,263, 10,296, 10,329, 10,362, 10,395, 10,428,
//    10,461, 10,494, 10,527, 10,560, 10,593, 10,626, 10,659, 10,692, 10,725, 10,758,
//    10,791, 10,824, 10,857, 10,890, 10,923, 10,956, 10,989, 11,022, 11,055, 11,088,
//    11,121, 11,154, 11,187, 11,220, 11,253, 11,286, 11,319, 11,352, 11,385, 11,418,
//    11,451, 11,484, 11,517, 11,550, 11,583, 11,616, 11,649, 11,682, 11,715, 11,748,
//    11,781, 11,814, 11,847, 11,880, 11,913, 11,946, 11,979, 12,012, 12,045, 12,078,
//    12,111, 12,144, 12,177, 12,210, 12,243, 12,276, 12,309, 12,342, 12,375, 12,408,
//    12,441, 12,474, 12,507, 12,540, 12,573, 12,606, 12,639, 12,672, 12,705, 12,738,
//    12,771, 12,804, 12,837, 12,870, 12,903, 12,936, 12,969, 13,002, 13,035, 13,068,
//    13,101, 13,134, 13,167, 13,200, 13,233, 13,266,
//    Cancellation requested in continuation...
//
//
//    Cancellation request issued...
//
//    TaskCanceledException: A task was canceled.
//
//    Antecedent Status: RanToCompletion
//    Continuation Status: Canceled
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim rnd As New Random()
      Dim lockObj As New Object()
      Dim cts As New CancellationTokenSource()
      Dim token As CancellationToken = cts.Token
      Dim timer As New Timer(AddressOf Elapsed, cts, 5000, Timeout.Infinite)

      Dim t = Task.Run( Function()
                           Dim product33 As New List(Of Integer)()
                           For ctr As Integer = 1 To Int16.MaxValue
                              ' Check for cancellation.
                              If token.IsCancellationRequested Then
                                 Console.WriteLine("\nCancellation requested in antecedent...\n")
                                 token.ThrowIfCancellationRequested()
                              End If
                              ' Introduce a delay.
                              If ctr Mod 2000 = 0 Then
                                 Dim delay As Integer
                                 SyncLock lockObj
                                    delay = rnd.Next(16,501)
                                 End SyncLock
                                 Thread.Sleep(delay)
                              End If

                              ' Determine if this is a multiple of 33.
                              If ctr Mod 33 = 0 Then product33.Add(ctr)
                           Next
                           Return product33.ToArray()
                        End Function, token)

      Dim continuation = t.ContinueWith(Sub(antecedent)
                                           Console.WriteLine("Multiples of 33:" + vbCrLf)
                                           Dim arr = antecedent.Result
                                           For ctr As Integer = 0 To arr.Length - 1
                                              If token.IsCancellationRequested Then
                                                 Console.WriteLine("{0}Cancellation requested in continuation...{0}",
                                                                   vbCrLf)
                                                 token.ThrowIfCancellationRequested()
                                              End If

                                              If ctr Mod 100 = 0 Then
                                                 Dim delay As Integer
                                                 SyncLock lockObj
                                                    delay = rnd.Next(16,251)
                                                 End SyncLock
                                                 Thread.Sleep(delay)
                                              End If
                                              Console.Write("{0:N0}{1}", arr(ctr),
                                                            If(ctr <> arr.Length - 1, ", ", ""))
                                              If Console.CursorLeft >= 74 Then Console.WriteLine()
                                           Next
                                           Console.WriteLine()
                                        End Sub, token)

      Try
         continuation.Wait()
      Catch e As AggregateException
         For Each ie In e.InnerExceptions
            Console.WriteLine("{0}: {1}", ie.GetType().Name,
                              ie.Message)
         Next
      Finally
         cts.Dispose()
      End Try

      Console.WriteLine(vbCrLf + "Antecedent Status: {0}", t.Status)
      Console.WriteLine("Continuation Status: {0}", continuation.Status)
  End Sub

   Private Sub Elapsed(state As Object)
      Dim cts As CancellationTokenSource = TryCast(state, CancellationTokenSource)
      If cts Is Nothing Then return

      cts.Cancel()
      Console.WriteLine("{0}Cancellation request issued...{0}", vbCrLf)
   End Sub
End Module
' The example displays output like the following:
'    Multiples of 33:
'
'    33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
'    561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
'    1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
'    1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
'    1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
'    2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
'    2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
'    2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
'    3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
'    3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
'    3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
'    4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
'    4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
'    5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
'    5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
'    5,775, 5,808, 5,841, 5,874, 5,907, 5,940, 5,973, 6,006, 6,039, 6,072, 6,105,
'    6,138, 6,171, 6,204, 6,237, 6,270, 6,303, 6,336, 6,369, 6,402, 6,435, 6,468,
'    6,501, 6,534, 6,567, 6,600, 6,633, 6,666, 6,699, 6,732, 6,765, 6,798, 6,831,
'    6,864, 6,897, 6,930, 6,963, 6,996, 7,029, 7,062, 7,095, 7,128, 7,161, 7,194,
'    7,227, 7,260, 7,293, 7,326, 7,359, 7,392, 7,425, 7,458, 7,491, 7,524, 7,557,
'    7,590, 7,623, 7,656, 7,689, 7,722, 7,755, 7,788, 7,821, 7,854, 7,887, 7,920,
'    7,953, 7,986, 8,019, 8,052, 8,085, 8,118, 8,151, 8,184, 8,217, 8,250, 8,283,
'    8,316, 8,349, 8,382, 8,415, 8,448, 8,481, 8,514, 8,547, 8,580, 8,613, 8,646,
'    8,679, 8,712, 8,745, 8,778, 8,811, 8,844, 8,877, 8,910, 8,943, 8,976, 9,009,
'    9,042, 9,075, 9,108, 9,141, 9,174, 9,207, 9,240, 9,273, 9,306, 9,339, 9,372,
'    9,405, 9,438, 9,471, 9,504, 9,537, 9,570, 9,603, 9,636, 9,669, 9,702, 9,735,
'    9,768, 9,801, 9,834, 9,867, 9,900, 9,933, 9,966, 9,999, 10,032, 10,065, 10,098,
'    10,131, 10,164, 10,197, 10,230, 10,263, 10,296, 10,329, 10,362, 10,395, 10,428,
'    10,461, 10,494, 10,527, 10,560, 10,593, 10,626, 10,659, 10,692, 10,725, 10,758,
'    10,791, 10,824, 10,857, 10,890, 10,923, 10,956, 10,989, 11,022, 11,055, 11,088,
'    11,121, 11,154, 11,187, 11,220, 11,253, 11,286, 11,319, 11,352, 11,385, 11,418,
'    11,451, 11,484, 11,517, 11,550, 11,583, 11,616, 11,649, 11,682, 11,715, 11,748,
'    11,781, 11,814, 11,847, 11,880, 11,913, 11,946, 11,979, 12,012, 12,045, 12,078,
'    12,111, 12,144, 12,177, 12,210, 12,243, 12,276, 12,309, 12,342, 12,375, 12,408,
'    12,441, 12,474, 12,507, 12,540, 12,573, 12,606, 12,639, 12,672, 12,705, 12,738,
'    12,771, 12,804, 12,837, 12,870, 12,903, 12,936, 12,969, 13,002, 13,035, 13,068,
'    13,101, 13,134, 13,167, 13,200, 13,233, 13,266,
'    Cancellation requested in continuation...
'
'
'    Cancellation request issued...
'
'    TaskCanceledException: A task was canceled.
'
'    Antecedent Status: RanToCompletion
'    Continuation Status: Canceled

También se puede evitar que una continuación se ejecute si su antecedente se cancela sin proporcionar a la continuación un token de cancelación. Para ello, debe especificarse la opción TaskContinuationOptions.NotOnCanceled al crear la continuación.You can also prevent a continuation from executing if its antecedent is canceled without supplying the continuation a cancellation token by specifying the TaskContinuationOptions.NotOnCanceled option when you create the continuation. Este es un ejemplo sencillo.The following is a simple example.

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

public class Example
{
   public static void Main()
   {
      var cts = new CancellationTokenSource();
      CancellationToken token = cts.Token;
      cts.Cancel();

      var t = Task.FromCanceled(token);
      var continuation = t.ContinueWith( (antecedent) => {
                                            Console.WriteLine("The continuation is running.");
                                          } , TaskContinuationOptions.NotOnCanceled);
      try {
         t.Wait();
      }
      catch (AggregateException ae) {
         foreach (var ie in ae.InnerExceptions)
            Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);

         Console.WriteLine();
      }
      finally {
         cts.Dispose();
      }

      Console.WriteLine("Task {0}: {1:G}", t.Id, t.Status);
      Console.WriteLine("Task {0}: {1:G}", continuation.Id,
                        continuation.Status);
   }
}
// The example displays the following output:
//       TaskCanceledException: A task was canceled.
//
//       Task 1: Canceled
//       Task 2: Canceled
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim cts As New CancellationTokenSource()
      Dim token As CancellationToken = cts.Token
      cts.Cancel()

      Dim t As  Task = Task.FromCanceled(token)
      Dim continuation As Task = t.ContinueWith(Sub(antecedent)
                                                   Console.WriteLine("The continuation is running.")
                                                End Sub, TaskContinuationOptions.NotOnCanceled)
      Try
         t.Wait()
      Catch e As AggregateException
         For Each ie In e.InnerExceptions
            Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
         Next
         Console.WriteLine()
      Finally
         cts.Dispose()
      End Try

      Console.WriteLine("Task {0}: {1:G}", t.Id, t.Status)
      Console.WriteLine("Task {0}: {1:G}", continuation.Id,
                        continuation.Status)
   End Sub
End Module
' The example displays the following output:
'       TaskCanceledException: A task was canceled.
'
'       Task 1: Canceled
'       Task 2: Canceled

Cuando una continuación entra en el estado Canceled puede afectar a las continuaciones posteriores, en función de los valores de TaskContinuationOptions que se especificaron para esas continuaciones.After a continuation goes into the Canceled state, it may affect continuations that follow, depending on the TaskContinuationOptions that were specified for those continuations.

Las continuaciones eliminadas no se iniciarán.Continuations that are disposed will not start.

Continuaciones y tareas secundariasContinuations and Child Tasks

Una continuación no se ejecuta hasta que el antecedente y todas sus tareas secundarias asociadas no se completen.A continuation does not run until the antecedent and all of its attached child tasks have completed. La continuación no espera a que las tareas secundarias desasociadas finalicen.The continuation does not wait for detached child tasks to finish. En los dos ejemplos siguientes se ilustran tareas secundarias que se asocian y desasocian de un antecedente que crea una continuación.The following two examples illustrate child tasks that are attached to and detached from an antecedent that creates a continuation. En el ejemplo siguiente la continuación solo se ejecuta después de que se completan todas las tareas secundarias. Si se ejecuta el ejemplo varias veces, se genera una salida idéntica cada vez.In the following example, the continuation runs only after all child tasks have completed, and running the example multiple times produces identical output each time. En el ejemplo se inicia el antecedente mediante una llamada al método TaskFactory.StartNew, ya que de forma predeterminada el método Task.Run crea una tarea primaria cuya opción de creación de tarea predeterminada es TaskCreationOptions.DenyChildAttach.The example launches the antecedent by calling the TaskFactory.StartNew method, since by default the Task.Run method creates a parent task whose default task creation option is TaskCreationOptions.DenyChildAttach.

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

public class Example
{
   public static void Main()
   {
      var t = Task.Factory.StartNew( () => { Console.WriteLine("Running antecedent task {0}...",
                                                  Task.CurrentId);
                                             Console.WriteLine("Launching attached child tasks...");
                                             for (int ctr = 1; ctr <= 5; ctr++)  {
                                                int index = ctr;
                                                Task.Factory.StartNew( (value) => {
                                                                       Console.WriteLine("   Attached child task #{0} running",
                                                                                         value);
                                                                       Thread.Sleep(1000);
                                                                     }, index, TaskCreationOptions.AttachedToParent);
                                             }
                                             Console.WriteLine("Finished launching attached child tasks...");
                                           });
      var continuation = t.ContinueWith( (antecedent) => { Console.WriteLine("Executing continuation of Task {0}",
                                                                             antecedent.Id);
                                                         });
      continuation.Wait();
   }
}
// The example displays the following output:
//       Running antecedent task 1...
//       Launching attached child tasks...
//       Finished launching attached child tasks...
//          Attached child task #5 running
//          Attached child task #1 running
//          Attached child task #2 running
//          Attached child task #3 running
//          Attached child task #4 running
//       Executing continuation of Task 1
Imports System
Imports System.Threading
Imports System.Threading.Tasks

Public Module Example
   Public Sub Main()
      Dim t = Task.Factory.StartNew( Sub()
                                        Console.WriteLine("Running antecedent task {0}...",
                                                          Task.CurrentId)
                                        Console.WriteLine("Launching attached child tasks...")
                                        For ctr As Integer = 1 To 5
                                           Dim index As Integer = ctr
                                           Task.Factory.StartNew( Sub(value)
                                                                     Console.WriteLine("   Attached child task #{0} running",
                                                                                       value)
                                                                     Thread.Sleep(1000)
                                                                  End Sub, index, TaskCreationOptions.AttachedToParent)
                                        Next
                                        Console.WriteLine("Finished launching attached child tasks...")
                                     End Sub)
      Dim continuation = t.ContinueWith( Sub(antecedent)
                                            Console.WriteLine("Executing continuation of Task {0}",
                                                              antecedent.Id)
                                         End Sub)
      continuation.Wait()
   End Sub
End Module
' The example displays the following output:
'       Running antecedent task 1...
'       Launching attached child tasks...
'       Finished launching attached child tasks...
'          Attached child task #5 running
'          Attached child task #1 running
'          Attached child task #2 running
'          Attached child task #3 running
'          Attached child task #4 running
'       Executing continuation of Task 1

No obstante, si las tareas secundarias se desasocian del antecedente, la continuación se ejecuta en cuanto finaliza el antecedente y con independencia del estado de las tareas secundarias.If child tasks are detached from the antecedent, however, the continuation runs as soon as the antecedent has terminated, regardless of the state of the child tasks. Como resultado, varias ejecuciones del ejemplo siguiente pueden tener una salida distinta que depende de cómo el programador de tareas controla cada tarea secundaria.As a result, multiple runs of the following example can produce variable output that depends on how the task scheduler handled each child task.

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

public class Example
{
   public static void Main()
   {
      var t = Task.Factory.StartNew( () => { Console.WriteLine("Running antecedent task {0}...",
                                                  Task.CurrentId);
                                             Console.WriteLine("Launching attached child tasks...");
                                             for (int ctr = 1; ctr <= 5; ctr++)  {
                                                int index = ctr;
                                                Task.Factory.StartNew( (value) => {
                                                                       Console.WriteLine("   Attached child task #{0} running",
                                                                                         value);
                                                                       Thread.Sleep(1000);
                                                                     }, index);
                                             }
                                             Console.WriteLine("Finished launching detached child tasks...");
                                           }, TaskCreationOptions.DenyChildAttach);
      var continuation = t.ContinueWith( (antecedent) => { Console.WriteLine("Executing continuation of Task {0}",
                                                                             antecedent.Id);
                                                         });
      continuation.Wait();
   }
}
// The example displays output like the following:
//       Running antecedent task 1...
//       Launching attached child tasks...
//       Finished launching detached child tasks...
//          Attached child task #1 running
//          Attached child task #2 running
//          Attached child task #5 running
//          Attached child task #3 running
//       Executing continuation of Task 1
//          Attached child task #4 running
Imports System
Imports System.Threading
Imports System.Threading.Tasks

Public Module Example
   Public Sub Main()
      Dim t = Task.Factory.StartNew( Sub()
                                        Console.WriteLine("Running antecedent task {0}...",
                                                          Task.CurrentId)
                                        Console.WriteLine("Launching attached child tasks...")
                                        For ctr As Integer = 1 To 5
                                           Dim index As Integer = ctr
                                           Task.Factory.StartNew( Sub(value)
                                                                     Console.WriteLine("   Attached child task #{0} running",
                                                                                       value)
                                                                     Thread.Sleep(1000)
                                                                  End Sub, index)
                                        Next
                                        Console.WriteLine("Finished launching detached child tasks...")
                                     End Sub, TaskCreationOptions.DenyChildAttach)
      Dim continuation = t.ContinueWith( Sub(antecedent)
                                            Console.WriteLine("Executing continuation of Task {0}",
                                                              antecedent.Id)
                                         End Sub)
      continuation.Wait()
   End Sub
End Module
' The example displays output like the following:
'       Running antecedent task 1...
'       Launching attached child tasks...
'       Finished launching detached child tasks...
'          Attached child task #1 running
'          Attached child task #2 running
'          Attached child task #5 running
'          Attached child task #3 running
'       Executing continuation of Task 1
'          Attached child task #4 running

El estado final de la tarea antecedente depende del estado final de las tareas secundarias asociadas.The final status of the antecedent task depends on the final status of any attached child tasks. El estado de las tareas secundarias desasociadas no afecta al elemento primario.The status of detached child tasks does not affect the parent. Para más información, consulte Attached and Detached Child Tasks (Tareas secundarias asociadas y desasociadas).For more information, see Attached and Detached Child Tasks.

Asociación de estado con continuacionesAssociating State with Continuations

Un estado arbitrario se puede asociar con una continuación de tarea.You can associate arbitrary state with a task continuation. El método ContinueWith proporciona versiones sobrecargadas, y cada una de ellas toma un valor Object que representa el estado de la continuación.The ContinueWith method provides overloaded versions that each take an Object value that represents the state of the continuation. Más adelante se puede tener acceso a este objeto de estado mediante la propiedad Task.AsyncState .You can later access this state object by using the Task.AsyncState property. Si no se proporciona un valor, este objeto de estado es null .This state object is null if you do not provide a value.

El estado de continuación es útil al convertir un código existente que use el modelo de programación asincrónica (APM) para utilizar la TPL.Continuation state is useful when you convert existing code that uses the Asynchronous Programming Model (APM) to use the TPL. En el APM, normalmente se proporciona el estado del objeto en el método BeginMétodo y el acceso posterior a ese estado mediante la propiedad IAsyncResult.AsyncState.In the APM, you typically provide object state in the BeginMethod method and later access that state by using the IAsyncResult.AsyncState property. Si se usa el método ContinueWith , se puede conservar este estado al convertir código que usa el APM para usar la TPL.By using the ContinueWith method, you can preserve this state when you convert code that uses the APM to use the TPL.

El estado de continuación también puede ser útil cuando se trabaja con objetos Task en el depurador de Visual Studio.Continuation state can also be useful when you work with Task objects in the Visual Studio debugger. Por ejemplo, en la ventana Tareas paralelas , la columna Tarea muestra la representación de cadena del objeto de estado de cada tarea.For example, in the Parallel Tasks window, the Task column displays the string representation of the state object for each task. Para más información sobre la ventana Tareas paralelas, consulte el artículo Usar la ventana Tareas.For more information about the Parallel Tasks window, see Using the Tasks Window.

En el ejemplo siguiente se muestra cómo usar el estado de continuación.The following example shows how to use continuation state. En él se crea una cadena de tareas de continuación.It creates a chain of continuation tasks. Cada tarea proporciona la hora actual —un objeto DateTime — para el parámetro state del método ContinueWith .Each task provides the current time, a DateTime object, for the state parameter of the ContinueWith method. Cada objeto DateTime representa la hora en que se creó la tarea de continuación.Each DateTime object represents the time at which the continuation task is created. Cada tarea produce como resultado un segundo objeto DateTime que representa la hora en que finaliza la tarea.Each task produces as its result a second DateTime object that represents the time at which the task finishes. Una vez que finalizan todas las tareas, se muestran la hora de creación y la hora de finalización de cada tarea de continuación.After all tasks finish, this example displays the creation time and the time at which each continuation task finishes.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

// Demonstrates how to associate state with task continuations.
class ContinuationState
{
   // Simluates a lengthy operation and returns the time at which
   // the operation completed.
   public static DateTime DoWork()
   {
      // Simulate work by suspending the current thread 
      // for two seconds.
      Thread.Sleep(2000);

      // Return the current time.
      return DateTime.Now;
   }

   static void Main(string[] args)
   {
      // Start a root task that performs work.
      Task<DateTime> t = Task<DateTime>.Run(delegate { return DoWork(); });

      // Create a chain of continuation tasks, where each task is 
      // followed by another task that performs work.
      List<Task<DateTime>> continuations = new List<Task<DateTime>>();
      for (int i = 0; i < 5; i++)
      {
         // Provide the current time as the state of the continuation.
         t = t.ContinueWith(delegate { return DoWork(); }, DateTime.Now);
         continuations.Add(t);
      }

      // Wait for the last task in the chain to complete.
      t.Wait();

      // Print the creation time of each continuation (the state object)
      // and the completion time (the result of that task) to the console.
      foreach (var continuation in continuations)
      {
         DateTime start = (DateTime)continuation.AsyncState;
         DateTime end = continuation.Result;

         Console.WriteLine("Task was created at {0} and finished at {1}.",
            start.TimeOfDay, end.TimeOfDay);
      }
   }
}

/* Sample output:
Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.
*/
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

' Demonstrates how to associate state with task continuations.
Public Module ContinuationState
   ' Simluates a lengthy operation and returns the time at which
   ' the operation completed.
   Public Function DoWork() As Date
      ' Simulate work by suspending the current thread 
      ' for two seconds.
      Thread.Sleep(2000)

      ' Return the current time.
      Return Date.Now
   End Function

   Public Sub Main()
      ' Start a root task that performs work.
      Dim t As Task(Of Date) = Task(Of Date).Run(Function() DoWork())

      ' Create a chain of continuation tasks, where each task is
      ' followed by another task that performs work.
      Dim continuations As New List(Of Task(Of DateTime))()
      For i As Integer = 0 To 4
         ' Provide the current time as the state of the continuation.
         t = t.ContinueWith(Function(antecedent, state) DoWork(), DateTime.Now)
         continuations.Add(t)
      Next

      ' Wait for the last task in the chain to complete.
      t.Wait()

      ' Display the creation time of each continuation (the state object)
      ' and the completion time (the result of that task) to the console.
      For Each continuation In continuations
         Dim start As DateTime = CDate(continuation.AsyncState)
         Dim [end] As DateTime = continuation.Result

         Console.WriteLine("Task was created at {0} and finished at {1}.",
            start.TimeOfDay, [end].TimeOfDay)
      Next
   End Sub
End Module
' The example displays output like the following:
'       Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
'       Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
'       Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
'       Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
'       Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.

Control de excepciones producidas por continuacionesHandling Exceptions Thrown from Continuations

Una relación antecedente-continuación no es una relación entre elementos primarios y secundarios.An antecedent-continuation relationship is not a parent-child relationship. Las excepciones producidas por las continuaciones no se propagan al antecedente.Exceptions thrown by continuations are not propagated to the antecedent. Por lo tanto, las excepciones producidas por las continuaciones se deben controlar como en cualquier otra tarea, es decir:Therefore, handle exceptions thrown by continuations as you would handle them in any other task, as follows:

  • Puede usar el método Wait, WaitAllo WaitAny , o su homólogo genérico, para esperar en la continuación.You can use the Wait, WaitAll, or WaitAny method, or its generic counterpart, to wait on the continuation. Puede esperar un antecedente y sus continuaciones en la misma instrucción try , tal como se muestra en el ejemplo siguiente.You can wait for an antecedent and its continuations in the same try statement, as shown in the following example.

    using System;
    using System.Threading.Tasks;
    
    public class Example
    {
       public static void Main()
       {
          var task1 = Task<int>.Run( () => { Console.WriteLine("Executing task {0}",
                                                               Task.CurrentId);
                                             return 54; });
          var continuation = task1.ContinueWith( (antecedent) =>
                                                 { Console.WriteLine("Executing continuation task {0}",
                                                                     Task.CurrentId);
                                                   Console.WriteLine("Value from antecedent: {0}",
                                                                     antecedent.Result);
                                                   throw new InvalidOperationException();
                                                } );
    
          try {
             task1.Wait();
             continuation.Wait();
          }
          catch (AggregateException ae) {
              foreach (var ex in ae.InnerExceptions)
                  Console.WriteLine(ex.Message);
          }
       }
    }
    // The example displays the following output:
    //       Executing task 1
    //       Executing continuation task 2
    //       Value from antecedent: 54
    //       Operation is not valid due to the current state of the object.
    
    Imports System.Threading.Tasks
    
    Module Example
       Public Sub Main()
          Dim task1 = Task(Of Integer).Run(Function()
                                              Console.WriteLine("Executing task {0}",
                                                                Task.CurrentId)
                                              Return 54
                                           End Function)
          Dim continuation = task1.ContinueWith(Sub(antecedent)
                                                   Console.WriteLine("Executing continuation task {0}",
                                                                     Task.CurrentId)
                                                   Console.WriteLine("Value from antecedent: {0}",
                                                                     antecedent.Result)
                                                   Throw New InvalidOperationException()
                                                End Sub)
    
          Try
               task1.Wait()
               continuation.Wait()
          Catch ae As AggregateException
              For Each ex In ae.InnerExceptions
                  Console.WriteLine(ex.Message)
              Next
          End Try
       End Sub
    End Module
    ' The example displays the following output:
    '       Executing task 1
    '       Executing continuation task 2
    '       Value from antecedent: 54
    '       Operation is not valid due to the current state of the object.
    
  • Puede usar una segunda continuación para observar la propiedad Exception de la primera continuación.You can use a second continuation to observe the Exception property of the first continuation. En el siguiente ejemplo, una tarea intenta leer en un archivo inexistente.In the following example, a task attempts to read from a non-existent file. La continuación muestra entonces información sobre la excepción en la tarea antecedente.The continuation then displays information about the exception in the antecedent task.

    using System;
    using System.IO;
    using System.Threading.Tasks;
    
    public class Example
    {
       public static void Main()
       {
          var t = Task.Run( () => { string s = File.ReadAllText(@"C:\NonexistentFile.txt");
                                    return s;
                                  });
    
          var c = t.ContinueWith( (antecedent) =>
                                  { // Get the antecedent's exception information.
                                    foreach (var ex in antecedent.Exception.InnerExceptions) {
                                       if (ex is FileNotFoundException)
                                          Console.WriteLine(ex.Message);
                                    }
                                  }, TaskContinuationOptions.OnlyOnFaulted);
    
          c.Wait();
       }
    }
    // The example displays the following output:
    //        Could not find file 'C:\NonexistentFile.txt'.
    
    Imports System.IO
    Imports System.Threading.Tasks
    
    Module Example
       Public Sub Main()
          Dim t = Task.Run( Function()
                               Dim s As String = File.ReadAllText("C:\NonexistentFile.txt")
                               Return s
                            End Function)
    
          Dim c = t.ContinueWith( Sub(antecedent)
                                     ' Get the antecedent's exception information.
                                     For Each ex In antecedent.Exception.InnerExceptions
                                        If TypeOf ex Is FileNotFoundException
                                           Console.WriteLine(ex.Message)
                                        End If
                                     Next
                                  End Sub, TaskContinuationOptions.OnlyOnFaulted)
    
          c.Wait()
       End Sub
    End Module
    ' The example displays the following output:
    '       Could not find file 'C:\NonexistentFile.txt'.
    

    Dado que se ejecutó con la opción TaskContinuationOptions.OnlyOnFaulted , la continuación solo se ejecuta si se produce una excepción en el antecedente y, por lo tanto, puede asumir que la propiedad Exception del antecedente no es null.Because it was run with the TaskContinuationOptions.OnlyOnFaulted option, the continuation executes only if an exception occurs in the antecedent, and therefore it can assume that the antecedent's Exception property is not null. Si la continuación se ejecuta tanto si se produce una excepción en el antecedente como si no, habría que comprobar si la propiedad Exception del antecedente no es null antes de intentar controlar la excepción, como se muestra en el fragmento de código siguiente.If the continuation executes whether or not an exception is thrown in the antecedent, it would have to check whether the antecedent's Exception property is not null before attempting to handle the exception, as the following code fragment shows.

    // Determine whether an exception occurred.
    if (antecedent.Exception != null) {
       foreach (var ex in antecedent.Exception.InnerExceptions) {
          if (ex is FileNotFoundException)
             Console.WriteLine(ex.Message);
       }
    }
    
    ' Determine whether an exception occurred.
     If antecedent.Exception IsNot Nothing Then
       ' Get the antecedent's exception information.
        For Each ex In antecedent.Exception.InnerExceptions
           If TypeOf ex Is FileNotFoundException
              Console.WriteLine(ex.Message)
           End If
        Next
     End If
    

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

  • Si la continuación es una tarea secundaria asociada que se creó mediante la opción TaskContinuationOptions.AttachedToParent , sus excepciones serán propagadas por el elemento primario hacia el subproceso de llamada, como sucede con cualquier otro elemento secundario asociado.If the continuation is an attached child task that was created by using the TaskContinuationOptions.AttachedToParent option, its exceptions will be propagated by the parent back to the calling thread, as is the case in any other attached child. Para más información, consulte Attached and Detached Child Tasks (Tareas secundarias asociadas y desasociadas).For more information, see Attached and Detached Child Tasks.

Vea tambiénSee also