Exportar (0) Imprimir
Expandir todo
Este artículo se tradujo de forma manual. Mueva el puntero sobre las frases del artículo para ver el texto original. Más información.
Traducción
Original

Paralelismo de tareas (Task Parallel Library)

La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) se basa en el concepto de una 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 y que ofrecen el equilibrio de carga para maximizar el rendimiento. Esto 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 dos motivos, en .NET Framework, TPL es la API preferida para escribir código multiproceso, asincrónico y paralelo.

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 Nota

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


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


Nota 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. La 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 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.

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. Observe que 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 Example
{
   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 the following output:
//       Hello from thread 'Main'.
//       Hello from taskA.


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

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 the following output:
//       Hello from thread 'Main'.
//       Hello from taskA.


También se puede usar el método TaskFactory.StartNew para crear e iniciar una tarea en una sola operación. Utilice este método cuando la creación y la programación no tengan que ser independientes y necesite opciones de creación de tareas adicionales o el uso de un programador concreto, o cuando necesita pasar información de estado adicional en la tarea mediante su propiedad AsyncState, como se muestra en el ejemplo siguiente.


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

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

      // Better: Create and start the task in one operation. 
      Task taskA = Task.Factory.StartNew(() => 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 the following output:
//       Hello from thread 'Main'.
//       Hello from taskA.


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 Example
{
   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


Para obtener más información, vea Cómo: Devolver un valor 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 el valor final, no el valor tal y como se 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;

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

public class Example
{
   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 output:
//       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.


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 Example
{
   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.


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;

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

public class Example
{
   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}, {3}",
                              data.Name, data.CreationTime, data.ThreadNum, task.Status);
      }                     
   }
}
// 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.


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 forma diferida, lo que significa que no se crea hasta que se solicita; por tanto, una tarea podrá tener un identificador diferente cada vez que se ejecute el programa. Para obtener más información acerca de cómo ver los identificadores de tareas en el depurador, vea Usar la ventana Tareas y Uso de la ventana Tareas paralelas.

La mayoría de las API que crean tareas proporcionan sobrecargas que aceptan un parámetro TaskCreationOptions. Al especificar una de estas opciones, se le está indicando al programador cómo se programa la tarea en el grupo de subprocesos. En la tabla siguiente se muestran las diversas opciones de creación de tareas.

TaskCreationOptions valor de parámetro

Descripción

None

Es la opción predeterminada si no se especifica ninguna opción. El programador usa su heurística predeterminada para programar la tarea.

PreferFairness

Especifica que la tarea debe programarse de modo que las tareas creadas anteriormente tengan más posibilidades de ejecutarse antes y que las tareas posteriormente tengan más posibilidades de ejecutarse después.

LongRunning

Especifica que la tarea representa una operación de ejecución prolongada.

AttachedToParent

Especifica que una tarea debe crearse como un elemento secundario asociado de la tarea actual, si existe. Para obtener más información, vea Tareas secundarias asociadas y desasociadas.

DenyChildAttach

Especifica que, si una tarea interna especifica la opción AttachedToParent, esa tarea no se convertirá en una tarea secundaria asociada.

HideScheduler

Especifica que el programador de tareas para tareas creadas llamando a métodos como TaskFactory.StartNew o Task<TResult>.ContinueWith desde dentro de una tarea determinada es el programador predeterminado en lugar del programador en el que se ejecuta esta tarea.

Las opciones pueden combinarse 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();


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 el estado de dicha tarea y, al recuperar el valor de la propiedad Task<TResult>.Result, puede usar la salida del antecedente como entrada de 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 Task<TResult>.Result de la tarea getData. 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 Task<TResult>.Result de la tarea processData. La tarea displayData toma el resultado de la tarea processData y 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 Example
{
   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


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. Observe que 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 Example
{
   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


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

Para obtener más información, vea Tareas de continuación y Cómo: Encadenar varias tareas con continuaciones.

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.


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

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 diez tareas secundarias adjuntas. Observe que aunque en el ejemplo se llame al método Task.Wait para esperar a que la tarea primaria finalice, no tiene que esperar explícitamente a que se completen las tareas secundarias asociadas.


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

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
                      Console.WriteLine("Parent task 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.


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

Los tipos System.Threading.Tasks.Task y System.Threading.Tasks.Task<TResult> proporcionan varias sobrecargas de los métodos Task.Wait y Task<TResult>.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 en cuanto 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...


Para obtener un ejemplo que muestra el control de excepciones, vea Cómo: Controlar excepciones iniciadas por tareas.

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 Wait producirá las tareas generadas por una tarea incluso si se llama a este método Wait una vez completada la tarea.

Para obtener más información, vea Cómo: Esperar a que una o varias tareas se completen.

Las clases Task y Task<TResult> proporcionan varios métodos que pueden ayudarle a crear varias tareas para implementar patrones comunes y mejorar el uso de las características de lenguaje asincrónicas proporcionadas por C#, Visual Basic y F#. En esta sección se describen los métodos WhenAll, WhenAny, Delay y FromResult<TResult>.

Dd537609.collapse_all(es-es,VS.110).gif 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.

Dd537609.collapse_all(es-es,VS.110).gifTask.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 adicionales.

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

  • Operaciones que han expirado. 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.

Dd537609.collapse_all(es-es,VS.110).gifTask.Delay

El método Task.Delay produce un objeto Task que finaliza tras el tiempo especificado. Puede usar este método para crear bucles que sondeen ocasionalmente en busca de datos, introduzcan finales de tiempo de espera, retrasen el control de los datos proporcionados por el usuario durante un tiempo predeterminado, etc.

Dd537609.collapse_all(es-es,VS.110).gifTask(T).FromResult

Mediante el método Task.FromResult<TResult>, 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 que utiliza FromResult<TResult> para recuperar los resultados de las operaciones de descarga asincrónica que se retienen en caché, vea Cómo: Crear tareas precalculadas.

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 combina con la tarea, que normalmente es el subproceso que está esperando a que la tarea termine o al subproceso que tiene acceso a la propiedad Result. Este comportamiento sirve para aplicar 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 utilizando cualquiera de los siguientes elementos de 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 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 obtener más información sobre excepciones y tareas, vea Control de excepciones (Task Parallel Library) y Cómo: Controlar excepciones iniciadas por 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 obtener más información, vea Cancelación de tareas y Cómo: Cancelar una tarea y sus elementos secundarios.

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.

En algunos casos, es posible que desee usar un objeto Task para encapsular alguna operación asincrónica ejecutada por un componente externo en lugar de su propio 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, como por ejemplo, su compatibilidad con la propagación de excepciones y el uso de continuaciones. Para obtener más información, vea TaskCompletionSource<TResult>.

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 (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) permite configurar algunos valores del programador de tareas predeterminado e incluso permite proporcionar un programador personalizado. Para obtener más información, vea TaskScheduler.

TPL tiene varios tipos públicos nuevos que resultan útiles tanto en escenarios en paralelo como en escenarios secuenciales. Entre ellos, se incluyen diversas clases de colecciones multiproceso rápidas y escalables del espacio de nombres System.Collections.Concurrent y varios tipos nuevos de sincronización, como System.Threading.Semaphore y System.Threading.ManualResetEventSlim, que resultan más eficaces que sus predecesores en tipos concretos 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 obtener más información, vea Estructuras de datos para la programación paralela.

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 obtener más información sobre los métodos de extensión, vea Métodos de extensión (Guía de programación de C#) y Métodos de extensión (Visual Basic).

Si debe heredar de Task o Task<TResult>, no puede usar las clases Run, Run, o System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult>, o System.Threading.Tasks.TaskCompletionSource<TResult> para crear instancias del tipo de tarea personalizada porque estos mecanismos solo crean objetos Task y objetos 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 porque también estos mecanismos crean solo objetos Task y Task<TResult>.

Título

Descripción

Tareas de continuación

Describe el funcionamiento de las continuaciones.

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 (Task Parallel Library)

Describe cómo se controlan excepciones en subprocesos simultáneos.

Cómo: Usar Parallel.Invoke para ejecutar operaciones paralelas

Describe cómo usar Invoke.

Cómo: Devolver un valor de una tarea

Describe cómo devolver valores de tareas.

Cómo: Esperar a que una o varias tareas se completen

Describe cómo esperar a que finalicen las tareas.

Cómo: Cancelar una tarea y sus elementos secundarios

Describe cómo cancelar tareas.

Cómo: Controlar excepciones iniciadas por tareas

Describe cómo controlar las excepciones iniciadas por tareas.

Cómo: Encadenar varias tareas con continuaciones

Describe cómo ejecutar una tarea cuando se completa otra tarea.

Cómo: Crear tareas precalculadas

Describe cómo utilizar el método Task.FromResult<TResult> 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 paralelas

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 (Task Parallel Library)

Describe cómo usar For y ForEach para crear bucles paralelos sobre los datos.

Programación paralela en .NET Framework

Nodo de nivel superior de la programación en paralelo de .NET Framework.

Adiciones de comunidad

AGREGAR
Mostrar:
© 2014 Microsoft