Programación asincrónica basada en tareas

La biblioteca TPL se basa en el concepto de tarea, que representa una operación asincrónica. De cierta forma, una tarea recuerda a un subproceso o elemento de trabajo ThreadPool, pero en un nivel más alto de abstracción. El término paralelismo de tareas hace referencia a la ejecución simultánea de una o varias tareas independientes. Las tareas proporcionan dos ventajas fundamentales:

  • Un uso más eficaz y más escalable de los recursos del sistema.

    En segundo plano, las tareas se ponen en la cola del elemento ThreadPool, que se ha mejorado con algoritmos que determinan y ajustan el número de subprocesos. Estos algoritmos proporcionan equilibrio de carga para maximizar el rendimiento. Este proceso hace que las tareas resulten relativamente ligeras y que, por tanto, pueda crearse un gran número de ellas para habilitar un paralelismo pormenorizado.

  • Un mayor control mediante programación del que se puede conseguir con un subproceso o un elemento de trabajo.

    Las tareas y el marco que se crea en torno a ellas proporcionan un amplio conjunto de API que admiten el uso de esperas, cancelaciones, continuaciones, control robusto de excepciones, estado detallado, programación personalizada, y más.

Por estos motivos, en .NET, TPL es la API preferida para escribir código multiproceso, asincrónico y paralelo.

Crear y ejecutar tareas implícitamente

El método Parallel.Invoke proporciona una manera conveniente de ejecutar cualquier número de instrucciones arbitrarias simultáneamente. Pase un delegado Action por cada elemento de trabajo. La manera más fácil de crear estos delegados es con expresiones lambda. La expresión lambda puede llamar a un método con nombre o proporcionar el código alineado. En el siguiente ejemplo se muestra una llamada a Invoke básica que crea e inicia dos tareas que se ejecutan a la vez. La primera tarea se representa mediante una expresión lambda que llama a un método denominado DoSomeWork y la segunda tarea se representa mediante una expresión lambda que llama a un método denominado DoSomeOtherWork.

Nota

En esta documentación, se utilizan expresiones lambda para definir delegados en la TPL. Si no está familiarizado con las expresiones lambda de C# o Visual Basic, vea Expresiones lambda en PLINQ y TPL.

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

Nota

El número de instancias de Task que Invoke crea en segundo plano no es necesariamente igual al número de delegados que se proporcionan. TPL puede emplear varias optimizaciones, sobre todo con grandes números de delegados.

Para obtener más información, vea Cómo: Usar Parallel.Invoke para ejecutar operaciones en paralelo.

Para tener un mayor control de la ejecución de tareas o para devolver un valor de la tarea, debe trabajar con objetos Task más explícitamente.

Crear y ejecutar tareas explícitamente

Una tarea que no devuelve un valor se representa mediante la clase System.Threading.Tasks.Task. Una tarea que devuelve un valor se representa mediante la clase System.Threading.Tasks.Task<TResult>, que se hereda de Task. El objeto de tarea controla los detalles de la infraestructura y proporciona métodos y propiedades a los que se puede obtener acceso desde el subproceso que realiza la llamada a lo largo de la duración de la tarea. Por ejemplo, se puede tener acceso a la propiedad Status de una tarea en cualquier momento para determinar si ha empezado a ejecutarse, si se ha ejecutado hasta su finalización, si se ha cancelado o si se ha producido una excepción. El estado se representa mediante la enumeración TaskStatus.

Cuando se crea una tarea, se proporciona un delegado de usuario que encapsula el código que la tarea va a ejecutar. El delegado se puede expresar como un delegado con nombre, un método anónimo o una expresión lambda. Las expresiones lambda pueden contener una llamada a un método con nombre, tal y como se muestra en el siguiente ejemplo. El ejemplo incluye una llamada al método Task.Wait para garantizar que la ejecución de la tarea se complete antes de que finalice la aplicación de modo de consola.

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

public class Lambda
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Create a task and supply a user delegate by using a lambda expression.
      Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
      // Start the task.
      taskA.Start();

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                        Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Lambda
    Module Example
        Public Sub Main()
            Thread.CurrentThread.Name = "Main"

            ' Create a task and supply a user delegate by using a lambda expression. 
            Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
            ' Start the task.
            taskA.Start()

            ' Output a message from the calling thread.
            Console.WriteLine("Hello from thread '{0}'.",
                            Thread.CurrentThread.Name)
            taskA.Wait()
        End Sub
    End Module
    ' The example displays output like the following:
    '    Hello from thread 'Main'.
    '    Hello from taskA.
End Namespace

También se pueden usar los métodos Task.Run para crear e iniciar una tarea en una sola operación. Para administrar la tarea, los métodos Run usan el programador de tareas predeterminado, independientemente de qué programador de tareas esté asociado al subproceso actual. Los métodos Run son el modo preferido para crear e iniciar tareas cuando no se necesita más control sobre la creación y la programación de la tarea.

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

namespace Run;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Run
    Module Example
        Public Sub Main()
            Thread.CurrentThread.Name = "Main"

            Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

            ' Output a message from the calling thread.
            Console.WriteLine("Hello from thread '{0}'.",
                            Thread.CurrentThread.Name)
            taskA.Wait()
        End Sub
    End Module
    ' The example displays output like the following:
    '    Hello from thread 'Main'.
    '    Hello from taskA.
End Namespace

También se puede usar el método TaskFactory.StartNew para crear e iniciar una tarea en una sola operación. Como se muestra en el ejemplo siguiente, puede usar este método cuando:

  • No es necesario separar la creación y la programación y se requieren opciones de creación de tareas adicionales o el uso de un programador específico.

  • Debe pasar un estado adicional a la tarea que puede recuperar a través de su propiedad Task.AsyncState.

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

namespace TaskIntro;

class CustomData
{
    public long CreationTime;
    public int Name;
    public int ThreadNum;
}

public class AsyncState
{
    public static void Main()
    {
        Task[] taskArray = new Task[10];
        for (int i = 0; i < taskArray.Length; i++)
        {
            taskArray[i] = Task.Factory.StartNew((Object obj) =>
            {
                CustomData data = obj as CustomData;
                if (data == null) return;

                data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
            },
            new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
        }
        Task.WaitAll(taskArray);
        foreach (var task in taskArray)
        {
            var data = task.AsyncState as CustomData;
            if (data != null)
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum);
        }
    }
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)

            For Each task In taskArray
                Dim data = TryCast(task.AsyncState, CustomData)
                If data IsNot Nothing Then
                    Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                    data.Name, data.CreationTime, data.ThreadNum)
                End If
            Next
        End Sub
    End Module
    ' The example displays output like the following:
    '     Task #0 created at 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

Task y Task<TResult> exponen en cada caso una propiedad Factory estática que devuelve una instancia predeterminada de TaskFactory, por lo que se puede llamar al método como Task.Factory.StartNew(). Asimismo, en el siguiente ejemplo, dado que las tareas son de tipo System.Threading.Tasks.Task<TResult>, cada una tiene una propiedad Task<TResult>.Result pública que contiene el resultado del cálculo. Las tareas se ejecutan de forma asincrónica y pueden completarse en cualquier orden. Si se obtiene acceso a la propiedad Result antes de que el cálculo se complete, la propiedad bloqueará el subproceso hasta que el valor esté disponible.

using System;
using System.Threading.Tasks;

public class Result
{
   public static void Main()
   {
        Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

        var results = new Double[taskArray.Length];
        Double sum = 0;

        for (int i = 0; i < taskArray.Length; i++) {
            results[i] = taskArray[i].Result;
            Console.Write("{0:N1} {1}", results[i],
                              i == taskArray.Length - 1 ? "= " : "+ ");
            sum += results[i];
        }
        Console.WriteLine("{0:N1}", sum);
   }

   private static Double DoComputation(Double start)
   {
      Double sum = 0;
      for (var value = start; value <= start + 10; value += .1)
         sum += value;

      return sum;
   }
}
// The example displays the following output:
//        606.0 + 10,605.0 + 100,495.0 = 111,706.0

Namespace Result
    Module Example
        Public Sub Main()
            Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
                Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
                Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

            Dim results(taskArray.Length - 1) As Double
            Dim sum As Double

            For i As Integer = 0 To taskArray.Length - 1
                results(i) = taskArray(i).Result
                Console.Write("{0:N1} {1}", results(i),
                    If(i = taskArray.Length - 1, "= ", "+ "))
                sum += results(i)
            Next
            Console.WriteLine("{0:N1}", sum)
        End Sub

        Private Function DoComputation(start As Double) As Double
            Dim sum As Double
            For value As Double = start To start + 10 Step .1
                sum += value
            Next
            Return sum
        End Function
    End Module
    ' The example displays the following output:
    '       606.0 + 10,605.0 + 100,495.0 = 111,706.0
End Namespace

Para obtener más información, vea Cómo: Devolver un valor a partir de una tarea.

Cuando se usa una expresión lambda para crear un delegado, se obtiene acceso a todas las variables que están visibles en ese momento en el código fuente. Sin embargo, en algunos casos, sobre todo en los bucles, una expresión lambda no captura la variable como cabría esperar. Captura solo la referencia de la variable, no el valor cuando transforma después de cada iteración. En el siguiente ejemplo se ilustra el problema. Pasa un contador de bucle a una expresión lambda que crea instancias de un objeto CustomData y usa el contador de bucle como identificador del objeto. Como muestra la salida del ejemplo, cada objeto CustomData tiene un identificador idéntico.

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

namespace Example.Iterations;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class IterationTwo
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in the loop
      // counter. This produces an unexpected result.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj) => {
                                                 var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading

Namespace IterationsTwo
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            ' Create the task object by using an Action(Of Object) to pass in the loop
            ' counter. This produces an unexpected result.
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks}
                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    i)
            Next
            Task.WaitAll(taskArray)
        End Sub
    End Module
    ' The example displays output like the following:
    '       Task #10 created at 635116418427727841 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427727841 on thread #3.
    '       Task #10 created at 635116418427747843 on thread #3.
    '       Task #10 created at 635116418427747843 on thread #3.
    '       Task #10 created at 635116418427737842 on thread #4.
End Namespace

Puede obtener acceso al valor en cada iteración si proporciona un objeto de estado a una tarea a través de su constructor. En el ejemplo siguiente se modifica el ejemplo anterior utilizando el contador de bucle al crear el objeto CustomData que, a su vez, se pasa a la expresión lambda. Como muestra el resultado del ejemplo, cada objeto CustomData tiene ahora un identificador único basado en el valor del contador de bucle cuando se creó la instancia del objeto.

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

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class IterationOne
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data
      // to the Task constructor. This is useful when you need to capture outer variables
      // from within a loop.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                  Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading

Namespace IterationsOne
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            ' Create the task object by using an Action(Of Object) to pass in custom data
            ' to the Task constructor. This is useful when you need to capture outer variables
            ' from within a loop. 
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)
        End Sub
    End Module
    ' The example displays output like the following:
    '       Task #0 created at 635116412924597583 on thread #3.
    '       Task #1 created at 635116412924607584 on thread #4.
    '       Task #3 created at 635116412924607584 on thread #4.
    '       Task #4 created at 635116412924607584 on thread #4.
    '       Task #2 created at 635116412924607584 on thread #3.
    '       Task #6 created at 635116412924607584 on thread #3.
    '       Task #5 created at 635116412924607584 on thread #4.
    '       Task #8 created at 635116412924607584 on thread #4.
    '       Task #7 created at 635116412924607584 on thread #3.
    '       Task #9 created at 635116412924607584 on thread #4.
End Namespace

Este estado se pasa como argumento al delegado de la tarea y permite tener acceso desde el objeto de tarea mediante la propiedad Task.AsyncState. El ejemplo siguiente es una variación del ejemplo anterior. Utiliza la propiedad AsyncState para mostrar información sobre los objetos CustomData pasados a la expresión lambda.

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

namespace TaskIntro;

class CustomData
{
    public long CreationTime;
    public int Name;
    public int ThreadNum;
}

public class AsyncState
{
    public static void Main()
    {
        Task[] taskArray = new Task[10];
        for (int i = 0; i < taskArray.Length; i++)
        {
            taskArray[i] = Task.Factory.StartNew((Object obj) =>
            {
                CustomData data = obj as CustomData;
                if (data == null) return;

                data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
            },
            new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
        }
        Task.WaitAll(taskArray);
        foreach (var task in taskArray)
        {
            var data = task.AsyncState as CustomData;
            if (data != null)
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum);
        }
    }
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)

            For Each task In taskArray
                Dim data = TryCast(task.AsyncState, CustomData)
                If data IsNot Nothing Then
                    Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                    data.Name, data.CreationTime, data.ThreadNum)
                End If
            Next
        End Sub
    End Module
    ' The example displays output like the following:
    '     Task #0 created at 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

Identificador de tarea

Cada tarea recibe un identificador entero que la identifica de manera inequívoca en un dominio de aplicación y al que se puede obtener acceso mediante la propiedad Task.Id. El identificador resulta útil para ver información sobre la tarea en las ventanas Pilas paralelas y Tareas del depurador de Visual Studio. El identificador se crea de manera diferida, lo que significa que no se crea hasta que se solicita. Por lo tanto, una tarea puede tener un identificador diferente cada vez que se ejecuta el programa. Para más información sobre cómo ver los identificadores de tarea en el depurador, consulte Usar la ventana Tareas y Usar la ventana Pilas paralelas.

Opciones de creación de tareas

La mayoría de las API que crean tareas proporcionan sobrecargas que aceptan un parámetro TaskCreationOptions. Al especificar una o más de estas opciones, se le indica al programador de tareas cómo programar la tarea en el grupo de subprocesos. Las opciones se pueden combinar con una operación OR bit a bit.

En el ejemplo siguiente se muestra una tarea que tiene las opciones LongRunning y PreferFairness:

var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),
                        TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

Tareas, subprocesos y referencia cultural

Cada subproceso tiene asociada una referencia cultural y una interfaz de usuario de referencia cultural, que se definen mediante las propiedades Thread.CurrentCulture y Thread.CurrentUICulture, respectivamente. La referencia cultural de un subproceso se usa en operaciones como dar formato, analizar, ordenar y comparar cadenas. La referencia cultural de la interfaz de usuario de un subproceso se usa en la búsqueda de recursos.

La referencia cultural del sistema define la referencia cultural predeterminada y la referencia cultural de la interfaz de usuario de un subproceso. Sin embargo, puede especificar una referencia cultural predeterminada para todos los subprocesos de un dominio de aplicación mediante las propiedades CultureInfo.DefaultThreadCurrentCulture y CultureInfo.DefaultThreadCurrentUICulture. Si establece explícitamente la referencia cultural de un subproceso e inicia un nuevo subproceso, el nuevo subproceso no hereda la referencia cultural del subproceso que realiza la llamada; en su lugar, su referencia cultural es la referencia cultural predeterminada del sistema. Pero en la programación basada en tareas, las tareas usan la referencia cultural del subproceso que realiza la llamada, aunque la tarea se ejecute de forma asincrónica en otro subproceso.

Esto se muestra en el ejemplo siguiente. Cambia la referencia cultural actual de la aplicación a francés (Francia). Si francés (Francia) ya es la referencia cultural actual, cambia a inglés (Estados Unidos). Después, invoca un delegado llamado formatDelegate que devuelve algunos números con el formato de valores de moneda de la nueva referencia cultural. Si una tarea invoca el delegado de forma sincrónica o asincrónica, la tarea usa la referencia cultural del subproceso que realiza la llamada.

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

public class Example
{
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));

                                             output += Environment.NewLine;
                                             return output;
                                           };

       Console.WriteLine("The example is running on thread {0}",
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}",
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);

       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());

       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:");
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);

       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate);
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As Decimal = {163025412.32D, 18905365.59D}
        Dim formatString As String = "C2"
        Dim formatDelegate As Func(Of String) = Function()
                                                    Dim output As String = String.Format("Formatting using the {0} culture on thread {1}.",
                                                                                         CultureInfo.CurrentCulture.Name,
                                                                                         Thread.CurrentThread.ManagedThreadId)
                                                    output += Environment.NewLine
                                                    For Each value In values
                                                        output += String.Format("{0}   ", value.ToString(formatString))
                                                    Next
                                                    output += Environment.NewLine
                                                    Return output
                                                End Function

        Console.WriteLine("The example is running on thread {0}",
                          Thread.CurrentThread.ManagedThreadId)
        ' Make the current culture different from the system culture.
        Console.WriteLine("The current culture is {0}",
                          CultureInfo.CurrentCulture.Name)
        If CultureInfo.CurrentCulture.Name = "fr-FR" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' Execute the delegate synchronously.
        Console.WriteLine("Executing the delegate synchronously:")
        Console.WriteLine(formatDelegate())

        ' Call an async delegate to format the values using one format string.
        Console.WriteLine("Executing a task asynchronously:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

        Console.WriteLine("Executing a task synchronously:")
        Dim t2 = New Task(Of String)(formatDelegate)
        t2.RunSynchronously()
        Console.WriteLine(t2.Result)
    End Sub
End Module

' The example displays the following output:
'
'          The example is running on thread 1
'          The current culture is en-US
'          Changed the current culture to fr-FR.
'
'          Executing the delegate synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

Nota

En las versiones de .NET Framework anteriores a .NET Framework 4.6, la referencia cultural de una tarea viene determinada por la referencia cultural del subproceso en el que se ejecuta, no por la del subproceso que realiza la llamada. En el caso de las tareas asincrónicas, la referencia cultural que usa la tarea podría ser diferente a la del subproceso que realiza la llamada.

Para obtener más información sobre las tareas asincrónicas y la referencia cultural, consulte la sección "Referencia cultural y operaciones asincrónicas basadas en tareas" en el artículo CultureInfo.

Crear continuaciones de tareas

Los métodos Task.ContinueWith y Task<TResult>.ContinueWith permiten especificar una tarea que se iniciará cuando finalice la tarea anterior. Al delegado de la tarea de continuación se le pasa una referencia a la tarea antecedente para que pueda examinar su estado. Y al recuperar el valor de la propiedad Task<TResult>.Result, puede usar la salida del antecedente como entrada para la continuación.

En el ejemplo siguiente, la tarea getData se inicia llamando al método TaskFactory.StartNew<TResult>(Func<TResult>). La tarea processData se inicia automáticamente cuando finaliza getData y displayData se inicia cuando finaliza processData. getData genera una matriz de enteros, accesible a la tarea processData mediante la propiedad getData de la tarea Task<TResult>.Result. La tarea processData procesa dicha matriz y devuelve un resultado cuyo tipo se deduce del tipo de valor devuelto de la expresión lambda pasada al método Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). La tarea displayData se ejecuta automáticamente cuando finaliza processData y el objeto Tuple<T1,T2,T3> devuelto por la expresión lambda processData es accesible para la tarea displayData mediante la propiedad processData de la tarea Task<TResult>.Result. La tarea displayData toma el resultado de la tarea processData. Genera un resultado cuyo tipo se deduce de forma similar y que se pone a disposición del programa en la propiedad Result.

using System;
using System.Threading.Tasks;

public class ContinuationOne
{
   public static void Main()
   {
      var getData = Task.Factory.StartNew(() => {
                                             Random rnd = new Random();
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;

                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } );
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2,
                                                                         x.Result.Item3);
                                                 } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Namespace ContinuationsOne
    Module Example
        Public Sub Main()
            Dim getData = Task.Factory.StartNew(Function()
                                                    Dim rnd As New Random()
                                                    Dim values(99) As Integer
                                                    For ctr = 0 To values.GetUpperBound(0)
                                                        values(ctr) = rnd.Next()
                                                    Next
                                                    Return values
                                                End Function)
            Dim processData = getData.ContinueWith(Function(x)
                                                       Dim n As Integer = x.Result.Length
                                                       Dim sum As Long
                                                       Dim mean As Double

                                                       For ctr = 0 To x.Result.GetUpperBound(0)
                                                           sum += x.Result(ctr)
                                                       Next
                                                       mean = sum / n
                                                       Return Tuple.Create(n, sum, mean)
                                                   End Function)
            Dim displayData = processData.ContinueWith(Function(x)
                                                           Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                                   x.Result.Item1, x.Result.Item2,
                                                                                   x.Result.Item3)
                                                       End Function)
            Console.WriteLine(displayData.Result)
        End Sub
    End Module
    ' The example displays output like the following:
    '   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace

Dado que Task.ContinueWith es un método de instancia, puede encadenar llamadas a métodos en lugar de crear instancias de un objeto Task<TResult> para cada tarea antecedente. El ejemplo siguiente es funcionalmente idéntico al anterior, con la diferencia de que encadena llamadas al método Task.ContinueWith. El objeto Task<TResult> devuelto por la cadena de llamadas al método es la tarea final de continuación.

using System;
using System.Threading.Tasks;

public class ContinuationTwo
{
   public static void Main()
   {
      var displayData = Task.Factory.StartNew(() => {
                                                 Random rnd = new Random();
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;

                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ).
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2,
                                                             x.Result.Item3);
                                     } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Namespace ContinuationsTwo
    Module Example
        Public Sub Main()
            Dim displayData = Task.Factory.StartNew(Function()
                                                        Dim rnd As New Random()
                                                        Dim values(99) As Integer
                                                        For ctr = 0 To values.GetUpperBound(0)
                                                            values(ctr) = rnd.Next()
                                                        Next
                                                        Return values
                                                    End Function). _
            ContinueWith(Function(x)
                             Dim n As Integer = x.Result.Length
                             Dim sum As Long
                             Dim mean As Double

                             For ctr = 0 To x.Result.GetUpperBound(0)
                                 sum += x.Result(ctr)
                             Next
                             mean = sum / n
                             Return Tuple.Create(n, sum, mean)
                         End Function). _
            ContinueWith(Function(x)
                             Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                 x.Result.Item1, x.Result.Item2,
                                                 x.Result.Item3)
                         End Function)
            Console.WriteLine(displayData.Result)
        End Sub
    End Module
    ' The example displays output like the following:
    '   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace

Los métodos ContinueWhenAll y ContinueWhenAny permiten continuar a partir de varias tareas.

Para más información, consulte Encadenar tareas mediante tareas de continuación.

Crear tareas secundarias desasociadas

Cuando el código de usuario que se está ejecutando en una tarea crea una nueva tarea y no especifica la opción AttachedToParent, la nueva tarea no se sincroniza con la tarea principal de ninguna manera especial. Este tipo de tarea no sincronizada se denomina tarea anidada desasociada o tarea secundaria desasociada. En el siguiente ejemplo se muestra una tarea que crea una tarea secundaria desasociada:

var outer = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Outer task beginning.");

    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000000);
        Console.WriteLine("Detached task completed.");
    });
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
//    Outer task beginning.
//    Outer task completed.
//    Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Outer task beginning.")
                                      Dim child = Task.Factory.StartNew(Sub()
                                                                            Thread.SpinWait(5000000)
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
'     Outer task beginning.
'     Outer task completed.
'    Detached child completed.

Nota

La tarea primaria no espera a que la tarea secundaria desasociada se complete.

Crear tareas secundarias

Cuando el código de usuario que se está ejecutando en una tarea crea una tarea con la opción AttachedToParent, la nueva tarea se conoce como una tarea secundaria asociada de la tarea principal. Puede usar la opción AttachedToParent para expresar el paralelismo de tareas estructurado, ya que la tarea primaria espera implícitamente a que todas las tareas secundarias asociadas finalicen. En el ejemplo siguiente se muestra una tarea principal que crea 10 tareas secundarias adjuntas. En el ejemplo se llama al método Task.Wait para esperar a que finalice la tarea primaria. No tiene que esperar explícitamente a que se completen las tareas secundarias adjuntas.

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

public class Child
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
                      Console.WriteLine("Parent task beginning.");
                      for (int ctr = 0; ctr < 10; ctr++) {
                         int taskNo = ctr;
                         Task.Factory.StartNew((x) => {
                                                  Thread.SpinWait(5000000);
                                                  Console.WriteLine("Attached child #{0} completed.",
                                                                    x);
                                               },
                                               taskNo, TaskCreationOptions.AttachedToParent);
                      }
                   });

      parent.Wait();
      Console.WriteLine("Parent task completed.");
   }
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.
Imports System.Threading

Namespace Child
    Module Example
        Public Sub Main()
            Dim parent = Task.Factory.StartNew(Sub()
                                                   Console.WriteLine("Parent task beginning.")
                                                   For ctr As Integer = 0 To 9
                                                       Dim taskNo As Integer = ctr
                                                       Task.Factory.StartNew(Sub(x)
                                                                                 Thread.SpinWait(5000000)
                                                                                 Console.WriteLine("Attached child #{0} completed.",
                                                                                                 x)
                                                                             End Sub,
                                                       taskNo, TaskCreationOptions.AttachedToParent)
                                                   Next
                                               End Sub)
            parent.Wait()
            Console.WriteLine("Parent task completed.")
        End Sub
    End Module
    ' The example displays output like the following:
    '       Parent task beginning.
    '       Attached child #9 completed.
    '       Attached child #0 completed.
    '       Attached child #8 completed.
    '       Attached child #1 completed.
    '       Attached child #7 completed.
    '       Attached child #2 completed.
    '       Attached child #6 completed.
    '       Attached child #3 completed.
    '       Attached child #5 completed.
    '       Attached child #4 completed.
    '       Parent task completed.
End Namespace

Una tarea principal puede usar la opción TaskCreationOptions.DenyChildAttach para evitar que otras tareas se asocien a la tarea primaria. Para más información, consulte Tareas secundarias asociadas y desasociadas.

Esperar hasta que las tareas finalicen

Los tipos System.Threading.Tasks.Task y System.Threading.Tasks.Task<TResult> proporcionan varias sobrecargas de los métodos Task.Wait que permiten esperar a que finalice una tarea. Además, las sobrecargas del método Task.WaitAll estático y del método Task.WaitAny permiten esperar a que finalicen alguna o todas las tareas de una matriz de tareas.

Normalmente, una tarea se espera por una de estas razones:

  • El subproceso principal depende del resultado final que se calcula mediante una tarea.

  • Hay que controlar las excepciones que pueden producirse en la tarea.

  • La aplicación puede finalizar antes de que todas las tareas hayan terminado de ejecutarse. Por ejemplo, las aplicaciones de consola finalizarán cuando se ejecute todo el código sincrónico en Main (el punto de entrada de la aplicación).

En el siguiente ejemplo se muestra el modelo básico donde el control de excepciones no está implicado:

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

// Continue on this thread...
Dim tasks() =
{
    Task.Factory.StartNew(Sub() MethodA()),
    Task.Factory.StartNew(Sub() MethodB()),
    Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.
Task.WaitAll(tasks)

' Continue on this thread...

Para un ejemplo que muestra el control de excepciones, consulte Control de excepciones.

Algunas sobrecargas permiten especificar un tiempo de espera, mientras que otras toman un objeto CancellationToken adicional como parámetro de entrada, de modo que la espera puede cancelarse mediante programación o en respuesta a los datos proporcionados por el usuario.

Cuando se espera a una tarea, se espera implícitamente a todos los elementos secundarios de esa tarea que se crearon con la opción TaskCreationOptions.AttachedToParent. Task.Wait devuelve un valor inmediatamente si la tarea ya se ha completado. Un método Task.Wait producirá todas las excepciones planteadas por una tarea, incluso si se llama al método Task.Wait después de que la tarea se completara.

Componer tareas

Las clases Task y Task<TResult> proporcionan varios métodos para ayudarle a componer varias tareas. Estos métodos implementan patrones comunes y hacen un mejor uso de las características de lenguaje asincrónico proporcionadas por C#, Visual Basic y F#. En esta sección se describen los métodos WhenAll, WhenAny, Delay y FromResult.

Task.WhenAll

El método Task.WhenAll espera de forma asincrónica a que finalicen varios Task objetos Task<TResult>. Proporciona versiones sobrecargadas que permiten esperar a conjuntos no uniformes de tareas. Por ejemplo, puede esperar a que varios objetos Task y Task<TResult> se completen de una llamada al método.

Task.WhenAny

El método Task.WhenAny espera de forma asincrónica a que finalice uno de varios Task objetos Task<TResult>. Como en el método Task.WhenAll, este método proporciona versiones sobrecargadas que permiten esperar a conjuntos no uniformes de tareas. El método WhenAny es especialmente útil en los siguientes escenarios:

  • Operaciones redundantes: considere un algoritmo o una operación que pueda realizarse de muchas maneras. Puede usar el método WhenAny para seleccionar la operación que finaliza primero y luego cancelar las operaciones restantes.

  • Operaciones intercaladas: puede iniciar varias operaciones, todas las cuales deben finalizar y utilizar el método WhenAny para procesar los resultados cuando finalice cada operación. Finalizada una operación, puede iniciar una o más tareas.

  • Operaciones limitadas: puede usar el método WhenAny para extender el escenario anterior limitando el número de operaciones simultáneas.

  • Operaciones expiradas: puede usar el método WhenAny para seleccionar entre una o más tareas y una tarea que termina después de un tiempo concreto, como una tarea devuelta por el método Delay. El método Delay se describe en la sección siguiente.

Task.Delay

El método Task.Delay produce un objeto Task que finaliza tras el tiempo especificado. Puede usar este método para compilar bucles que sondean los datos, especificar tiempos de espera, retrasar el control de la entrada del usuario, etc.

Task(T).FromResult

Mediante el método Task.FromResult, puede crear un objeto Task<TResult> que contenga un resultado previamente calculado. Este método es útil cuando se realiza una operación asincrónica que devuelve un objeto Task<TResult> y el resultado de ese objeto Task<TResult> ya se ha calculado. Para obtener un ejemplo en el que se usa FromResult para recuperar los resultados de las operaciones de descarga asincrónica que se retienen en caché, vea Procedimiento para crear tareas precalculadas.

Controlar excepciones en tareas

Cuando una tarea produce una o más excepciones, las excepciones se encapsulan en una excepción AggregateException. Esa excepción se propaga de nuevo al subproceso que se une a la tarea. Normalmente, es el subproceso que espera a que finalice la tarea o el subproceso que accede a la propiedad Result. Este comportamiento aplica la directiva de .NET Framework por la que, de manera predeterminada, todas las excepciones no controladas deben terminar el proceso. El código de llamada puede controlar las excepciones con cualquiera de los siguientes elementos del bloque try/catch:

El subproceso de unión también puede controlar excepciones; para ello, obtiene acceso a la propiedad Exception antes de que la tarea se recolecte como elemento no utilizado. Al obtener acceso a esta propiedad, impide que la excepción no controlada desencadene el comportamiento de propagación de la excepción que termina el proceso cuando el objeto ha finalizado.

Para más información sobre las excepciones y las tareas, consulte Control de excepciones.

Cancelar tareas

La clase Task admite la cancelación cooperativa y está completamente integrada con las clases System.Threading.CancellationTokenSource y System.Threading.CancellationToken, que se presentaron en .NET Framework versión 4. Muchos de los constructores de la clase System.Threading.Tasks.Task toman un objeto CancellationToken como parámetro de entrada. Muchas de las sobrecargas de StartNew y Run incluyen también un parámetro CancellationToken.

Puede crear el token y emitir la solicitud de cancelación posteriormente usando la clase CancellationTokenSource. A continuación, debe pasar el token a Task como argumento y hacer referencia al mismo token también en el delegado de usuario, que se encarga de responder a una solicitud de cancelación.

Para más información, vea Cancelación de tareas y Cómo: Cancelar una tarea y sus elementos secundarios.

Clase TaskFactory

La clase TaskFactory proporciona métodos estáticos que encapsulan algunos modelos comunes de creación e inicio de tareas y tareas de continuación.

Al objeto TaskFactory predeterminado se puede tener acceso como propiedad estática de la clase Task o de la clase Task<TResult>. También pueden crearse directamente instancias de TaskFactory y especificar varias opciones entre las que se incluyan las opciones CancellationToken, TaskCreationOptions, TaskContinuationOptions o TaskScheduler. Cualquier opción que se especifique al crear el generador de tareas se aplicará a todas las tareas que este generador cree, a menos que Task se cree usando la enumeración TaskCreationOptions, en cuyo caso las opciones de la tarea reemplazarán a las del generador de tareas.

Tareas sin delegados

En algunos casos, es posible que desee usar Task para encapsular alguna operación asincrónica ejecutada por un componente externo en lugar de su usuario delegado. Si la operación se basa en el patrón Begin/End del modelo de programación asincrónica, puede usar los métodos FromAsync. Si no es este el caso, puede usar el objeto TaskCompletionSource<TResult> para encapsular la operación en una tarea y, de este modo, aprovechar algunas de las ventajas de programación de Task. Por ejemplo, compatibilidad con la propagación de excepciones y las continuaciones. Para obtener más información, vea TaskCompletionSource<TResult>.

Programadores personalizados

La mayoría de los desarrolladores de aplicaciones o bibliotecas no prestan atención al procesador en el que se ejecuta la tarea, al modo en que la tarea sincroniza su trabajo con otras tareas o al modo en que se programa la tarea en el objeto System.Threading.ThreadPool. Solo necesitan que la ejecución en el equipo host sea lo más eficaz posible. Si necesita tener un control más minucioso sobre los detalles de programación, la biblioteca TPL permite configurar algunos valores del programador de tareas predeterminado e incluso permite proporcionar un programador personalizado. Para obtener más información, vea TaskScheduler.

La biblioteca TPL tiene varios tipos públicos nuevos que resultan útiles tanto en escenarios en paralelo como en escenarios secuenciales. Entre ellas se incluyen varias clases de colección seguras para subprocesos, rápidas y escalables en el espacio de nombres System.Collections.Concurrent y varios tipos de sincronización nuevos. Por ejemplo, System.Threading.Semaphore y System.Threading.ManualResetEventSlim, que son más eficaces que sus predecesores para determinados tipos de cargas de trabajo. Otros tipos nuevos de .NET Framework versión 4, como System.Threading.Barrier y System.Threading.SpinLock, proporcionan una funcionalidad que no estaba disponible en versiones anteriores. Para más información, consulte Estructuras de datos para la programación paralela.

Tipos de tarea personalizados

Se recomienda no heredar de System.Threading.Tasks.Task ni de System.Threading.Tasks.Task<TResult>. En su lugar, se recomienda usar la propiedad AsyncState para asociar los datos adicionales o el estado a un objeto Task o Task<TResult>. También puede usar métodos de extensión para extender la funcionalidad de las clases Task y Task<TResult>. Para más información sobre los métodos de extensión, consulte Métodos de extensión (C#) y Métodos de extensión (Visual Basic).

Si debe heredar de Task o Task<TResult>, no puede usar Run, o las clases System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult> o System.Threading.Tasks.TaskCompletionSource<TResult>, para crear instancias del tipo de tarea personalizada. No se pueden usar porque estas clases solo crean objetos Task y Task<TResult>. Además, no puede usar los mecanismos de continuación de tarea proporcionados por Task, Task<TResult>, TaskFactory y TaskFactory<TResult> para crear instancias del tipo de tarea personalizada. No se pueden usar porque estas clases también crean solo objetos Task y Task<TResult>.

Title Descripción
Encadenar tareas mediante tareas de continuación Describe el funcionamiento de las continuaciones.
Attached and Detached Child Tasks (Tareas secundarias asociadas y desasociadas) Describe la diferencia entre las tareas secundarias asociadas y desasociadas.
Cancelación de tareas Describe la compatibilidad con la cancelación que está integrada en el objeto Task.
Control de excepciones Describe cómo se controlan excepciones en subprocesos simultáneos.
Cómo: Usar Parallel.Invoke para ejecutar operaciones en paralelo Describe cómo usar Invoke.
Cómo: Devolver un valor a partir de una tarea Describe cómo devolver valores de tareas.
Cómo: Cancelar una tarea y sus elementos secundarios Describe cómo cancelar tareas.
Cómo: Crear tareas precalculadas Describe cómo utilizar el método Task.FromResult para recuperar los resultados de las operaciones asincrónicas de descarga que se retienen en una memoria caché.
Cómo: Recorrer un árbol binario con tareas en paralelo Describe cómo utilizar tareas para atravesar un árbol binario.
Cómo: Desencapsular una tarea anidada Demuestra cómo utilizar el método de extensión Unwrap.
Paralelismo de datos Describe cómo usar For y ForEach para crear bucles paralelos sobre los datos.
Programación en paralelo Nodo de nivel superior de la programación en paralelo de .NET Framework.

Vea también