Compartir a través de


Programación asincrónica basada en tareas

La biblioteca paralela de tareas (TPL) se basa en el concepto de una tarea de , 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 una o varias tareas independientes que se ejecutan simultáneamente. Las tareas proporcionan dos ventajas principales:

  • Uso más eficaz y 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 sean relativamente ligeras y puede crear muchas de ellas para habilitar el paralelismo específico.

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

    Las tareas y el marco creados en torno a ellas proporcionan un amplio conjunto de API que admiten esperas, cancelaciones, continuaciones, control de excepciones sólido, estado detallado, programación personalizada, etc.

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

Creación y ejecución de tareas implícitamente

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

Nota

En esta documentación se usan expresiones lambda para definir delegados en TPL. Si no está familiarizado con las expresiones lambda en C# o Visual Basic, consulte 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. El TPL puede emplear varias optimizaciones, especialmente con un gran número de delegados.

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

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.

Creación y ejecución de 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 hereda de Task. El objeto de tarea controla los detalles de la infraestructura y proporciona métodos y propiedades a los que se puede acceder desde el subproceso que realiza la llamada durante toda la duración de la tarea. Por ejemplo, puede acceder a la propiedad Status de una tarea en cualquier momento para determinar si se ha iniciado la ejecución, se ejecutó hasta la finalización, se canceló o ha producido una excepción. El estado se representa mediante una enumeración TaskStatus.

Al crear una tarea, se le asigna un delegado de usuario que encapsula el código que ejecutará la tarea. 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, como se muestra en el ejemplo siguiente. En el ejemplo se incluye una llamada al método Task.Wait para asegurarse de que la tarea completa la ejecución antes de que finalice la aplicación en 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 puede 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 del programador de tareas asociado al subproceso actual. Los métodos Run son la manera preferida de crear e iniciar tareas cuando no se necesita más control sobre la creación y 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 puede usar el método TaskFactory.StartNew para crear e iniciar una tarea en una operación. Como se muestra en el ejemplo siguiente, puede usar este método cuando:

  • No es necesario separar la creación y 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> cada uno expone una propiedad de Factory estática que devuelve una instancia predeterminada de TaskFactory, de modo que pueda llamar al método como Task.Factory.StartNew(). Además, en el ejemplo siguiente, dado que las tareas son de tipo System.Threading.Tasks.Task<TResult>, cada una de ellas tiene una propiedad pública Task<TResult>.Result que contiene el resultado del cálculo. Las tareas se ejecutan de forma asincrónica y pueden completarse en cualquier orden. Si se accede a la propiedad Result antes de que finalice el cálculo, la propiedad bloquea el subproceso que realiza la llamada 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, consulte Cómo: Devolver un Valor de una Tarea.

Cuando se usa una expresión lambda para crear un delegado, tiene acceso a todas las variables que están visibles en ese momento en el código fuente. Sin embargo, en ciertas situaciones, especialmente dentro de los bucles, una función lambda no captura la variable como se esperaba. Solo captura la referencia de la variable, no el valor, ya que muta después de cada iteración. En el ejemplo siguiente se muestra el problema. Pasa un contador de bucle a una expresión lambda que crea una instancia de un objeto CustomData y usa el contador de bucles como identificador del objeto. Como se muestra en 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 acceder al valor en cada iteración proporcionando un objeto de estado a una tarea a través de su constructor. En el ejemplo siguiente se modifica el ejemplo anterior mediante el contador de bucles al crear el objeto CustomData, que, a su vez, se pasa a la expresión lambda. Como se muestra en la salida del ejemplo, cada objeto CustomData ahora tiene un identificador único basado en el valor del contador de bucles en el momento en que se creó una 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 tareas y se puede tener acceso desde el objeto de tarea mediante la propiedad Task.AsyncState. El ejemplo siguiente es una variación en el ejemplo anterior. Usa 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

Id. de tarea

Cada tarea recibe un identificador entero que lo identifica de forma única en un dominio de aplicación y al que se puede acceder 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 perezosa, lo que significa que no se genera hasta que se solicita. Por lo tanto, una tarea puede tener un identificador diferente cada vez que se ejecuta el programa. Para obtener más información sobre cómo ver los identificadores de tarea en el depurador, vea Uso de la ventana de tareas y Uso de la ventana de 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 varias de estas opciones, se 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 una cultura asociada y una cultura de la interfaz de usuario, definidas por las propiedades Thread.CurrentCulture y Thread.CurrentUICulture, respectivamente. El idioma 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.

En el ejemplo siguiente se proporciona una ilustración sencilla. Cambia la configuración regional 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). A continuación, invoca un delegado denominado formatDelegate que devuelve algunos números formateados como valores monetarios en la nueva cultura. 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 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 referencia cultural del subproceso que realiza la llamada. En el caso de las tareas asincrónicas, la cultura usada por la tarea podría ser diferente de la cultura del hilo de llamada.

Para obtener más información sobre las tareas asincrónicas y la referencia cultural, vea la sección "Referencia cultural y operaciones asincrónicas basadas en tareas" del 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 antecedente . 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 mediante una llamada al método TaskFactory.StartNew<TResult>(Func<TResult>). La tarea processData se inicia automáticamente cuando finaliza getData y se inicia displayData cuando finaliza processData. getData genera una matriz de enteros, que es accesible para la tarea processData a través de la propiedad Task<TResult>.Result de la tarea getData. La tarea processData procesa esa matriz y devuelve un resultado cuyo tipo se deduce del tipo de valor devuelto de la expresión lambda que se pasa 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 de processData es accesible para la tarea displayData a través de la propiedad Task<TResult>.Result de la tarea processData. La tarea displayData toma el resultado de la tarea processData. Genera un resultado cuyo tipo se deduce de forma similar y que está disponible para el 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, salvo 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 desde 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 ejecuta en una tarea crea una nueva tarea y no especifica la opción AttachedToParent, la nueva tarea no se sincroniza con la tarea primaria 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 primaria puede usar la opción TaskCreationOptions.DenyChildAttach para evitar que otras tareas se adjunte a la tarea primaria. Para más información, consulte Tareas secundarias asociadas y desasociadas.

Esperar a que finalicen las tareas

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, esperarías una tarea por una de estas razones:

  • El hilo principal depende del resultado final calculado por una tarea.

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

  • Es posible que la aplicación finalice antes de que todas las tareas hayan completado la ejecución. Por ejemplo, las aplicaciones de consola finalizarán después de que se haya ejecutado todo el código sincrónico en Main (el punto de entrada de la aplicación).

En el ejemplo siguiente se muestra el patrón básico que no implica el control de excepciones:

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 ver un ejemplo que muestra el control de excepciones, vea 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á las excepciones generadas por una tarea, incluso si se llamó al método Task.Wait después de completar la tarea.

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, Delayy FromResult .

Task.WhenAll

El método Task.WhenAll espera asincrónicamente a que finalicen varios objetos de Task o 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 asincrónicamente a que finalicen uno de varios objetos Task o Task<TResult>. Como en el método Task.WhenAll, este método proporciona versiones sobrecargadas que permiten esperar a la finalización de conjuntos de tareas no uniformes. El método WhenAny es especialmente útil en los escenarios siguientes:

  • operaciones redundantes: considere la posibilidad de realizar un algoritmo o una operación de muchas maneras. Puede usar el método WhenAny para seleccionar la operación que finaliza primero y, a continuación, 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. Una vez finalizada una operación, puede iniciar una o varias 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 varias tareas y una tarea que finalice después de un tiempo específico, como una tarea que devuelve el método Delay. El método Delay se describe en la sección siguiente.

Task.Delay

El método Task.Delay genera un objeto Task que finaliza después del 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 calculado previamente. 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 está calculado. Para obtener un ejemplo que usa FromResult para recuperar los resultados de las operaciones de descarga asincrónicas almacenadas en una memoria caché, vea How to: Create Pre-Computed Tasks.

Control de excepciones en tareas

Cuando una tarea produce una o varias excepciones, las excepciones se encapsulan en una excepción de 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 que todas las excepciones no controladas de forma predeterminada deben finalizar el proceso. El código de llamada puede controlar las excepciones mediante cualquiera de los siguientes elementos en un 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 acceder a esta propiedad, se impide que la excepción no controlada desencadene el comportamiento de propagación de excepciones que finaliza el proceso cuando se finaliza el objeto.

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

Cancelación de tareas

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

Puede crear el token y emitir la solicitud de cancelación más adelante mediante 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.

La clase TaskFactory

La clase TaskFactory proporciona métodos estáticos que encapsulan patrones comunes para crear e iniciar tareas y tareas de continuación.

Se puede acceder al TaskFactory predeterminado como una propiedad estática en la clase Task o Task<TResult>. También puede crear instancias de un TaskFactory directamente y especificar varias opciones que incluyen un CancellationToken, una opción de TaskCreationOptions, una opción de TaskContinuationOptions o un TaskScheduler. Las opciones que se especifiquen al crear el generador de tareas se aplicarán a todas las tareas que crea a menos que se cree el Task mediante la enumeración TaskCreationOptions, en cuyo caso las opciones de la tarea invalidan las del generador de tareas.

Tareas sin delegados

En algunos casos, es posible que quiera usar un Task para encapsular alguna operación asincrónica que realice un componente externo en lugar del delegado de usuario. 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 así, puede usar el objeto TaskCompletionSource<TResult> para encapsular la operación en una tarea y, de esta forma, obtener algunas de las ventajas de la programabilidad de Task. Por ejemplo, compatibilidad con la propagación de excepciones y las continuaciones. Para obtener más información, consulte TaskCompletionSource<TResult>.

Programadores personalizados

La mayoría de los desarrolladores de aplicaciones o bibliotecas no les importa en qué procesador se ejecuta la tarea, cómo sincroniza su trabajo con otras tareas o cómo está planificada o programada en el System.Threading.ThreadPool. Solo requieren que se ejecute lo más eficazmente posible en el equipo host. Si necesita un control más específico sobre los detalles de programación, el TPL le permite configurar algunas opciones en el programador de tareas predeterminado e incluso le permite proporcionar un programador personalizado. Para obtener más información, consulte TaskScheduler.

El TPL tiene varios tipos públicos nuevos que son útiles en escenarios paralelos y secuenciales. Esto incluye varias clases de colección rápidas, seguras para hilos y escalables en el espacio de nombres System.Collections.Concurrent, y también 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 4, por ejemplo, System.Threading.Barrier y System.Threading.SpinLock, proporcionan funcionalidad que no estaba disponible en versiones anteriores. Para obtener más información, consulte Estructuras de datos para programación paralela.

Tipos de tareas personalizados

Se recomienda no heredar de System.Threading.Tasks.Task o System.Threading.Tasks.Task<TResult>. En su lugar, se recomienda usar la propiedad AsyncState para asociar datos o estados adicionales a un objeto Task o Task<TResult>. También puede usar métodos de extensión para ampliar la funcionalidad de las clases Task y Task<TResult>. Para obtener más información sobre los métodos de extensión, consulte Métodos de extensión y Métodos de extensión.

Si debe heredar de Task o Task<TResult>, no puede usar Run ni las clases de System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult>o System.Threading.Tasks.TaskCompletionSource<TResult> para crear instancias del tipo de tarea personalizado. 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 tareas proporcionados por Task, Task<TResult>, TaskFactoryy TaskFactory<TResult> para crear instancias del tipo de tarea personalizado. No se pueden usar porque estas clases también crean solo objetos Task y Task<TResult>.

Título Descripción
Encadenado de tareas mediante tareas de continuación Describe cómo funcionan las continuaciones.
Tareas secundarias asociadas y desasociadas Describe la diferencia entre las tareas secundarias asociadas y desasociadas.
Cancelación de Tareas Describe la compatibilidad de cancelación integrada en el objeto Task.
Manejo de Excepciones Describe cómo se controlan las excepciones en subprocesos simultáneos.
Cómo: Usar Parallel.Invoke para ejecutar operaciones paralelas Describe cómo usar Invoke.
Procedimiento: Devolución de un valor a partir de una tarea Describe cómo devolver valores de tareas.
Procedimiento: Cancelación de una tarea y sus elementos secundarios Describe cómo cancelar tareas.
Cómo: Crear tareas calculadas previamente Describe cómo usar el método Task.FromResult para recuperar los resultados de las operaciones de descarga asincrónicas que se mantienen en una memoria caché.
Cómo: Recorrer un árbol binario con tareas paralelas Describe cómo usar tareas para recorrer un árbol binario.
Procedimiento: Desajuste de una tarea anidada Muestra cómo usar el método de extensión Unwrap.
Paralelismo de datos Describe cómo usar For y ForEach para crear bucles paralelos a través de datos.
Programación en Paralelo Nodo de nivel superior para la programación en paralelo de .NET Framework.

Consulte también