Programación paralela en .NET Framework

41
 Programación paralela en .NET Framework Muchos equipos y estaciones de trabajo tienen dos o cuatro núcleos (es decir, CPU) que permiten ejecutar varios subprocesos simultáneamente. Se espera que los equipos en un futuro cercano tengan significativamente más núcleos. Para aprovecharse del hardware de hoy y del mañana, puede paralelizar el código para distribuir el trabajo entre varios procesadores. En el pasado, la paralelización requería manipulación de bajo nivel de los subprocesos y bloqueos. Visual Studio 2010 y .NET Framework 4 mejoran la compatibilidad para la programación paralela proporcionando un nuevo runtime, nuevos tipos de biblioteca de clases y nuevas herramientas de diagnóstico. Estas características simplifican el desarrollo en paralelo, de modo que pueda escribir código paralelo eficaz, específico y escalable de forma natural sin tener que trabajar directamente con subprocesos ni el bloque de subprocesos. La siguiente ilustración proporciona una información general de alto nivel de la arquitectura de programación paralela en .NET Framework 4. Task Parallel Library .NET Framework 4 La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) es un conjunto de API y tipos públicos de los espacios de nombres System.Threading.Tasks  y System.Threading  de .NET Framework versión 4. El propósito de la biblioteca TPL es aumentar la productividad de los desarrolladores al simplificar el proceso de agregar paralelismo y simultaneidad a las aplicaciones. La biblioteca TPL escala el grado de simultaneidad de forma dinámica para usar más eficazmente todos los procesadores que están disponibles. Además, la TPL se encarga de la división del trabajo, la programación de los subprocesos en ThreadPool , la compatibilidad con la cancelación, la administración de los estados y otros detalles de bajo nivel. Al utilizar la TPL, el usuario puede optimizar el rendimiento del código mientras se centra en el trabajo para el que el programa está diseñado. A partir de .NET Framework 4, la TPL es el modo preferido de escribir código paralelo y multiproceso. Sin embargo, no todo el código se presta para la paralelización; por ejemplo, si un bucle realiza solo una cantidad reducida de trabajo en cada iteración o no se ejecuta para un gran número de iteraciones, la sobrecarga de la paralelización puede dar lugar a una ejecución más lenta del código. Además, al igual que cualquier código multiproceso, la paralelización hace que la ejecución del programa sea más compleja. Aunque la TPL simplifica los escenarios de multithreading, recomendamos tener conocimientos básicos sobre conceptos de subprocesamiento, por ejemplo, bloqueos, interbloqueos y condiciones de carrera, para usar la TPL eficazmente. Paralelismo de datos (Task Parallel Library) El paralelismo de datos hace referencia a los escenarios en los que la misma operación se realiza simultáneamente (es decir, en paralelo) en elementos de una colección o matriz de origen. Varias sobrecargas de los métodos  ForEach y For admiten el paralelismo de los datos con sintaxis imperativa en la clase System.Threading.Tasks.Parallel . En las operaciones paralelas de datos, se crean particiones de la colección de origen para que varios subprocesos puedan funcionar simultáneamente en segmentos diferentes. TPL admite el paralelismo de datos a través de la clase System.Threading.Tasks.Parallel . Esta clase proporciona las implementaciones paralelas basadas en método de los bucles  for y foreach (For y For Each en Visual Basic). Se escribe la lógica del bucle para un bucle Parallel.For  o Parallel.ForEach  de forma muy similar a como se escribiría un bucle secuencial. No tiene que crear los subprocesos ni poner en la cola los elementos de trabajo. En bucles básicos, no es preciso tomar bloqueos. TPL administra todo el trabajo de bajo nivel. En el siguiente ejemplo de código se muestra un bucle foreach simple y su equivalente paralelo. // Sequential version

Transcript of Programación paralela en .NET Framework

Page 1: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 1/41

 

Programación paralela en .NET Framework

Muchos equipos y estaciones de trabajo tienen dos o cuatro núcleos (es decir, CPU) que permiten ejecutar varios subprocesos simultáneamente. Se espera q

los equipos en un futuro cercano tengan significativamente más núcleos. Para aprovecharse del hardware de hoy y del mañana, puede paralelizar el código pa

distribuir el trabajo entre varios procesadores. En el pasado, la paralelización requería manipulación de bajo nivel de los subprocesos y bloqueos. Visual Stud

2010 y .NET Framework 4 mejoran la compatibilidad para la programación paralela proporcionando un nuevo runtime, nuevos tipos de biblioteca de clases

nuevas herramientas de diagnóstico. Estas características simplifican el desarrollo en paralelo, de modo que pueda escribir código paralelo eficaz, específico

escalable de forma natural sin tener que trabajar directamente con subprocesos ni el bloque de subprocesos. La siguiente ilustración proporciona un

información general de alto nivel de la arquitectura de programación paralela en .NET Framework 4.

Task Parallel Library

.NET Framework 4 

La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) es un conjunto de API y tipos públicos de los espacios de nombrSystem.Threading.Tasks  y System.Threading de .NET Framework versión 4. El propósito de la biblioteca TPL es aumentar la productividad de los desarrollador

al simplificar el proceso de agregar paralelismo y simultaneidad a las aplicaciones. La biblioteca TPL escala el grado de simultaneidad de forma dinámica pa

usar más eficazmente todos los procesadores que están disponibles. Además, la TPL se encarga de la división del trabajo, la programación de los subprocesos e

ThreadPool, la compatibilidad con la cancelación, la administración de los estados y otros detalles de bajo nivel. Al utilizar la TPL, el usuario puede optimizar

rendimiento del código mientras se centra en el trabajo para el que el programa está diseñado.

A partir de .NET Framework 4, la TPL es el modo preferido de escribir código paralelo y multiproceso. Sin embargo, no todo el código se presta para

paralelización; por ejemplo, si un bucle realiza solo una cantidad reducida de trabajo en cada iteración o no se ejecuta para un gran número de iteraciones,

sobrecarga de la paralelización puede dar lugar a una ejecución más lenta del código. Además, al igual que cualquier código multiproceso, la paralelización hac

que la ejecución del programa sea más compleja. Aunque la TPL simplifica los escenarios de multithreading, recomendamos tener conocimientos básicos sobr

conceptos de subprocesamiento, por ejemplo, bloqueos, interbloqueos y condiciones de carrera, para usar la TPL eficazmente.

Paralelismo de datos (Task Parallel Library)

El paralelismo de datos hace referencia a los escenarios en los que la misma operación se realiza simultáneamente (es decir, en paralelo) en elementos de un

colección o matriz de origen. Varias sobrecargas de los métodos   ForEach  y For  admiten el paralelismo de los datos con sintaxis imperativa en la cla

System.Threading.Tasks.Parallel . En las operaciones paralelas de datos, se crean particiones de la colección de origen para que varios subprocesos pueda

funcionar simultáneamente en segmentos diferentes. TPL admite el paralelismo de datos a través de la clase System.Threading.Tasks.Parallel . Esta cla

proporciona las implementaciones paralelas basadas en método de los bucles  for y foreach (For y For Each en Visual Basic). Se escribe la lógica del bucle para u

bucle Parallel.For o Parallel.ForEach de forma muy similar a como se escribiría un bucle secuencial. No tiene que crear los subprocesos ni poner en la cola lo

elementos de trabajo. En bucles básicos, no es preciso tomar bloqueos. TPL administra todo el trabajo de bajo nivel. En el siguiente ejemplo de código

muestra un bucle foreach simple y su equivalente paralelo.

// Sequential version

Page 2: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 2/41

 

foreach (var item in sourceCollection)

{

Process(item);

}

// Parallel equivalent 

Parallel.ForEach(sourceCollection, item => Process(item));

Cuando un bucle paralelo se ejecuta, la TPL crea particiones del origen de datos para que el bucle pueda funcionar simultáneamente en varias partes. E

segundo plano, el programador de tareas crea particiones de la tarea según los recursos del sistema y la carga de trabajo. Cuando es posible, el programad

redistribuye el trabajo entre varios subprocesos y procesadores si se desequilibra la carga de trabajo.

Los métodos Parallel.ForEach y Parallel.For tienen varias sobrecargas que permiten detener o ejecutar la ejecución de bucles, supervisar el estado del bucle

otros subprocesos, mantener el estado de subprocesos locales, finalizar los objetos de subprocesos locales, controlar el grado de simultaneidad, etc. Los tipos d

aplicación auxiliar que habilitan esta funcionalidad son ParallelLoopState, ParallelOptions y ParallelLoopResult, CancellationToken y CancellationTokenSource. 

Escribir un bucle Parallel.For simple

using System;

using System.Diagnostics;

using System.Threading.Tasks;

namespace MultiplyMatrices 

{

internal class Program 

{

#region Sequential_Loopprivate static void MultiplyMatricesSequential(double[,] matA, double[,] matB,

double[,] result)

{

int matACols = matA.GetLength(1);

int matBCols = matB.GetLength(1);

int matARows = matA.GetLength(0);

for (int i = 0; i < matARows; i++)

{

for (int  j = 0; j < matBCols; j++)

{

for (int k = 0; k < matACols; k++)

{

result[i, j] += matA[i, k] * matB[k, j];

}

}

}

}

#endregion 

#region Parallel_Loop

private static void MultiplyMatricesParallel(double[,] matA, double[,] matB, double[,] result)

{

int matACols = matA.GetLength(1);

int matBCols = matB.GetLength(1);

int matARows = matA.GetLength(0);

// A basic matrix multiplication. 

// Parallelize the outer loop to partition the source array by rows.  

Parallel.For(0, matARows, i =>

{

for (int  j = 0; j < matBCols; j++)

{

// Use a temporary to improve parallel performance. 

double temp = 0;

for (int k = 0; k < matACols; k++)

{

temp += matA[i, k] * matB[k, j];

}

result[i, j] = temp;

}

}); // Parallel.For 

}

#endregion 

#region Main

private static void MainMatrix(string[] args)

{

// Set up matrices. Use small values to better view

// result matrix. Increase the counts to see greater

// speedup in the parallel loop vs. the sequential loop. 

int colCount = 180;

int rowCount = 2000;

int colCount2 = 270;

double[,] m1 = InitializeMatrix(rowCount, colCount);

Page 3: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 3/41

 

  double[,] m2 = InitializeMatrix(colCount, colCount2);

double[,] result = new double[rowCount, colCount2];

// First do the sequential version.  

Console.WriteLine("Executing sequential loop...");

Stopwatch stopwatch = new Stopwatch();

stopwatch.Start();

MultiplyMatricesSequential(m1, m2, result);

stopwatch.Stop();

Console.WriteLine("Sequential loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds);

// For the skeptics. 

OfferToPrint(rowCount, colCount2, result);

// Reset timer and results matrix.

stopwatch.Reset();

result = new double[rowCount, colCount2];

// Do the parallel loop. 

Console.WriteLine("Executing parallel loop...");

stopwatch.Start();

MultiplyMatricesParallel(m1, m2, result);

stopwatch.Stop();

Console.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds);

OfferToPrint(rowCount, colCount2, result);

// Keep the console window open in de bug mode. 

Console.WriteLine("Press any key to exit.");

Console.ReadKey();

}

#endregion 

#region Helper_Methods

private static double[,] InitializeMatrix(int rows, int cols){

double[,] matrix = new double[rows, cols];

Random r = new Random();

for (int i = 0; i < rows; i++)

{

for (int  j = 0; j < cols; j++)

{

matrix[i, j] = r.Next(100);

}

}

return matrix;

}

private static void OfferToPrint(int rowCount, int colCount, double[,] matrix)

{

Console.WriteLine("Computation complete. Print results? y/n");

char c = Console.ReadKey().KeyChar;

if (c == 'y' || c == 'Y')

{

Console.WindowWidth = 180;Console.WriteLine();

for (int x = 0; x < rowCount; x++)

{

Console.WriteLine("ROW {0}: ", x);

for (int y = 0; y < colCount; y++)

{

Console.Write("{0:#.##} ", matrix[x, y]);

}

Console.WriteLine();

}

}

}

#endregion 

}

}

Puede utilizar la sobrecarga más básica del método For si no necesita cancelar ni interrumpir las iteraciones, ni mantener un estado local de subproceso.

Al paralelizar un código, incluidos los bucles, un objetivo importante consiste en hacer tanto uso de los procesadores como sea posible, sin excederse hasta

punto de que la sobrecarga del procesamiento en paralelo anule las ventajas en el rendimiento. En este ejemplo determinado, solamente se paraleliza el buc

exterior, ya que en el bucle interior no se realiza demasiado trabajo. La combinación de una cantidad pequeña de trabajo y los efectos no deseados en

memoria caché puede producir la degradación del rendimiento en los bucles paralelos anidados. Por consiguiente, paralelizar el bucle exterior solo es la mej

manera de maximizar las ventajas de simultaneidad en la mayoría de los sistemas.

Delegado

El tercer parámetro de esta sobrecarga de  For  es un delegado de tipo Action<int> en C# o Action(Of Integer) en Visual Basic. Un delegado Action siemp

devuelve void, tanto si no tiene parámetros como si tiene uno o dieciséis. En Visual Basic, el comportamiento de Action se define con Sub. En el ejemplo se utiliz

Page 4: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 4/41

 

una expresión lambda para crear el delegado, pero también se puede crear de otras formas. Para obtener más información, vea  Expresiones lambda en PLINQ

TPL. 

Valor de iteración

El delegado toma un único parámetro de entrada cuyo valor es la iteración actual. El runtime proporciona este valor de iteración y su valor inicial es el índice de

primer elemento del segmento (partición) del origen que se procesa en el subproceso actual.

Si requiere más control sobre el nivel de simultaneidad, utilice una de las sobrecargas que toma un parámetro de entrada

System.Threading.Tasks.ParallelOptions , como: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32, ParallelLoopState>). 

Valor devuelto y control de excepciones

For devuelve un objeto System.Threading.Tasks.ParallelLoopResult  cuando se han completado todos los subprocesos. Este valor devuelto es útil si se detiene

se interrumpe la iteración del bucle de forma manual, ya que ParallelLoopResult almacena información como la última iteración que se ejecutó hasta finalizar.

se producen una o más excepciones en uno de los subprocesos, se inicia System.AggregateException . 

En el código de este ejemplo, no se usa el valor devuelto de For. 

Análisis y rendimiento

Puede utilizar el Asistente de rendimiento para ver el uso de la CPU en el equipo. Como experimento, aumente el número de columnas y filas en las matrice

Cuanto mayores son las matrices, mayor es la diferencia de rendimiento entre las versiones en paralelo y en serie del cálculo. Si la matriz es pequeña, la versió

en serie se ejecutará más rápidamente debido a la sobrecarga de la configuración del bucle paralelo.

Las llamadas sincrónicas a los recursos compartidos, como la consola o el sistema de archivos, degradarán de forma significativa el rendimiento de un bucle

paralelo. Al medir el rendimiento, intente evitar llamadas como Console.WriteLine dentro del bucle.

Detener o interrumpir un bucle Parallel.For

using System;

using System.Collections.Concurrent;

using System.Threading;

using System.Threading.Tasks;

namespace StopOrBreak 

{

internal class Test 

{private static void Main_StopOrBreak(string[] args)

{

StopLoop();

BreakAtThreshold();

Console.WriteLine("Press any key to exit.");

Console.ReadKey();

}

private static void StopLoop()

{

Console.WriteLine("Stop loop...");

double[] source = MakeDemoSource(1000, 1);

var results = new ConcurrentStack<double>();

Parallel.For(0, source.Length, (i, loopState) =>

{

if (i < 100)

{

double d = Compute(source[i]);

results.Push(d);

}else 

{

loopState.Stop();

return;

}

} // Close lambda expression. 

); // Close Parallel.For 

Console.WriteLine("Results contains {0} elements", results.Count);

}

private static void BreakAtThreshold()

{

double[] source = MakeDemoSource(10000, 1.0002);

Page 5: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 5/41

 

  var results = new ConcurrentStack<double>();

Parallel.For(0, source.Length, (i, loopState) =>

{

double d = Compute(source[i]);

results.Push(d);

if (d > .2)

{

// Might be called more than once! 

loopState.Break();

Console.WriteLine("Break called at iteration {0}. d = {1} ", i, d);

Thread.Sleep(1000);

}

});

Console.WriteLine("results contains {0} elements", results.Count);

}

private static double Compute(double d)

{

return Math.Sqrt(d);

}

private static double[] MakeDemoSource(int size, double valToFind)

{

var result = new double[size];

double initialval = .01;

for (int i = 0; i < size; i++)

{

initialval *= valToFind;

result[i] = initialval;

}

return result;

}

}

}

En un bucle ParallelFor() u [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1], no se puede usar la misma instrucción break o Exit que se utiliza en

bucle secuencial porque estas construcciones de lenguaje son válidas para los bucles, y un "bucle" paralelo es realmente un método, no un bucle. En su lugar, s

usan los métodos Break o Stop. Algunas de las sobrecargas de Parallel.For aceptan Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) en Visu

Basic) como parámetro de entrada. El runtime crea en segundo plano el objeto ParallelLoopState, al que puede dar cualquier nombre que desee en la expresió

lambda.

En el siguiente ejemplo, el método requiere sólo 100 valores de la secuencia de origen y no importa qué elementos se recuperan. En este caso se usa el métod

Stop, porque indica todas las iteraciones del bucle (incluidas las que comenzaron antes de la iteración actual en otros subprocesos), para detenerse en cuan

sea conveniente.

En el segundo método se recuperan todos los elementos hasta un índice especificado en la secuencia de origen. En este caso, se llama a  Break, porque cuando

llega al índice en un subproceso, es posible que todavía no se hayan procesado los elementos anteriores en el origen. La interrupción hará que otros subproceso

abandonen el trabajo en segmentos posteriores (si están ocupados en alguno) y que completen el procesamiento de todos los elementos anteriores antes dsalir del bucle.

Es importante entender que después de llamar a Stop o Break, otros subprocesos en un bucle pueden seguir ejecutándose durante algún tiempo, pero esto n

está bajo el control del desarrollador de la aplicación. Puede usar la propiedad ParallelLoopState.IsStopped  para comprobar si el bucle se ha detenido en ot

subproceso. En el siguiente ejemplo, si IsStopped es true, no se escriben más datos en la colección.

Escribir un bucle Parallel.For que tenga variables locales de subproceso

Si se usan datos locales de subproceso, se puede evitar la sobrecarga de sincronizar un número grande de accesos al estado compartido. En lugar de escribir e

un recurso compartido en cada iteración, calcula y almacena el valor hasta que se completan todas las iteraciones de la tarea. A continuación, puede escribir

resultado final una vez en el recurso compartido o pasarlo a otro método.

using System;

using System.Linq;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadLocalFor 

{

class Test 

{

static void Main()

{

int[] nums = Enumerable.Range(0, 125000000).ToArray();

long total = 0;

Parallel.For<long>(0, nums.Length, () => 0, ( j, loop, subtotal) =>

{

subtotal += nums[ j];

return subtotal;

Page 6: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 6/41

 

},

(x) => Interlocked.Add(ref  total, x)

);

Console.WriteLine("The total is {0}", total);

Console.WriteLine("Press any key to exit");

Console.ReadKey();

}

}

}

Los dos primeros parámetros de cada método For especifican los valores de iteración inicial y final. En esta sobrecarga del método, el tercer parámetro es dond

inicializa el estado local. " Estado local" en este contexto significa una variable cuya duración se extiende desde inmediatamente antes de la primera iteración d

bucle en el subproceso actual hasta inmediatamente después de la última iteración.

El tipo del tercer parámetro es Func<TResult>, donde TResult es el tipo de la variable que almacenará el estado local del subproceso. Tenga en cuenta que, e

este ejemplo, se usa una versión genérica del método y el parámetro de tipo es  long (Long en Visual Basic). El parámetro de tipo indica al compilador el tipo de

variable temporal que se usará para almacenar el estado local del subproceso. La expresión () => 0 (Function() 0 en Visual Basic) de este ejemplo significa que

variable local de subproceso se inicializa en cero. Si el parámetro de tipo es un tipo de referencia o un tipo de valor definido por el usuario, este ejemplo de Fun

se parecería al siguiente:

() => new MyClass()

El cuarto parámetro de tipo es donde define la lógica del bucle. IntelliSense muestra que tiene un tipo de Func<int, ParallelLoopState, long, long> o Func(

Integer, ParallelLoopState, Long, Long). La expresión lambda espera tres parámetros de entrada en este mismo orden que corresponde a estos tipos. El últim

parámetro de tipo es el tipo devuelto. En este caso, el tipo es long porque es lo que se especificó en el parámetro de tipo  For. Llamamos a esa variable subtoen la expresión lambda y la devolvemos. El valor devuelto se utiliza para inicializar el subtotal en cada iteración subsiguiente. También puede considerar est

último parámetro simplemente como un valor que se pasa a cada iteración y después al delegado localFinally cuando se completa la última iteración.

El quinto parámetro es donde se define el método al que se llamará una vez, cuando todas las iteraciones de este subproceso se hayan completado. El tipo d

parámetro de entrada corresponde de nuevo al parámetro de tipo del método  For y al tipo que devuelve la expresión lambda del cuerpo. En este ejemplo,

valor se agrega a una variable en el ámbito de clase de una manera segura para subprocesos. Al usar una variable local de subproceso, hemos evitado escribir e

esta variable de clase en cada iteración de cada subproceso.

Escribir un bucle Parallel.ForEach que tenga variables locales de subproceso

Para utilizar una variable local de subproceso en un bucle ForEach, debe utilizar la versión del método que toma dos parámetros type. El primer parámetro

especifica el tipo del elemento de origen y el segundo parámetro especifica el tipo de la variable local de subproceso.

El primer parámetro de entrada es el origen de datos y el segundo es la función que inicializará la variable local de subproceso. El tercer parámetro de entrada

un Func<T1, T2, T3, TResult> que invoca el bucle paralelo en cada iteración. Se proporciona el código para el delegado y el bucle pasa los parámetros de entrad

Los parámetros de entrada son el elemento vigente, una variable ParallelLoopState que permite examinar el estado del bucle, y la variable local de subproces

Devuelve la variable local de subproceso y, a continuación, el método pasa a la iteración siguiente de esta partición. Esta variable es distinta en todas la

particiones del bucle.

El último parámetro de entrada del método ForEach es el delegado Action<T> que el método invocará cuando todos los bucles se hayan completado. El méto

proporciona el valor final de la variable local de subproceso para este subproceso (o partición del bucle) y proporciona el código que captura el valor final

realiza cualquier acción necesaria para combinar el resultado de esta partición con los resultados de las otras particiones. Como el tipo de delegado es  Action<T

no hay valor devuelto.

using System;

using System.Linq;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadLocalForEach 

{

internal class Test 

{

private static void Main()

{

int[] nums = Enumerable.Range(0, 1000000).ToArray();

long total = 0;

// First type parameter is the type of the s ource elements 

// Second type parameter is the type of the local data (subtotal) 

Parallel.ForEach<int, long>(nums, // source collection 

() => 0, // method to initialize the local variable  

( j, loop, subtotal) => // method invoked by the loop on each iteration  

{

Page 7: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 7/41

 

  subtotal += nums[ j]; //modify local variable 

return subtotal; // value to be passed to next iteration  

},

// Method to be executed when all loops have completed.  

// finalResult is the final value of subtotal. supplied by the ForEach method. 

(finalResult) => Interlocked.Add(ref  total, finalResult)

);

Console.WriteLine("The total from Parallel.ForEach is {0}", total);

Console.WriteLine("Press any key to exit");

Console.ReadKey();

}

}

}

Cancelar un bucle Parallel.For o ForEach

Los métodos Parallel.ForEach y Parallel.For admiten la cancelación a través del uso de tokens de cancelación. Para obtener más información sobre la cancelació

en general, vea Cancelación. En un bucle paralelo, se proporciona CancellationToken al método en el parámetro ParallelOptions y después se agrega la llama

paralela en un bloque try-catch.

Si el token que señala la cancelación es el mismo que se especifica en la instancia de ParallelOptions, el bucle paralelo producirá u

OperationCanceledException   única en la cancelación. Si algún otro token produce la cancelación, el bucle producirá una AggregateException   c

OperationCanceledException  como InnerException.

using System;

using System.Linq;

using System.Threading;

using System.Threading.Tasks;

namespace CancelParallelLoops 

{

internal class Program 

{

private static void Main()

{

int[] nums = Enumerable.Range(0, 10000000).ToArray();

CancellationTokenSource cts = new CancellationTokenSource();

// Use ParallelOptions instance to store the CancellationToken 

ParallelOptions po = new ParallelOptions();

po.CancellationToken = cts.Token;

po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

Console.WriteLine("Press any key to start. Press 'c' to cancel." );

Console.ReadKey();

// Run a task so that we can cancel from another thread.  

Task.Factory.StartNew(() =>

{

if (Console.ReadKey().KeyChar == 'c')

cts.Cancel();Console.WriteLine("press any key to exit");

});

try 

{

Parallel.ForEach(nums, po, (num) =>

{

double d = Math.Sqrt(num);

Console.WriteLine("{0} on {1}", d, Thread.CurrentThread.ManagedThreadId);

po.CancellationToken.ThrowIfCancellationRequested();

});

}

catch (OperationCanceledException e)

{

Console.WriteLine(e.Message);

}

Console.ReadKey();

}

}

}

Controlar excepciones en bucles paralelos

Las sobrecargas ParallelFor()y ForEach() no tienen ningún mecanismo especial para controlar las excepciones que puedan iniciarse. A este respecto, se asemeja

a bucles for y foreach normales (For y For Each en Visual Basic).

Cuando agregue su propia lógica de control de excepciones a los bucles paralelos, tenga en cuenta la posibilidad de que se inicien excepciones similares en vario

subprocesos al mismo tiempo, así como el caso de que una excepción iniciada en un subproceso puede hacer que se inicie otra excepción en otro subproces

Page 8: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 8/41

 

Puede administrar ambos casos si encapsula todas las excepciones del bucle en   System.AggregateException . En el ejemplo siguiente se muestra un posib

enfoque.

using System;

using System.Collections.Concurrent;

using System.Threading.Tasks;

namespace Paralell_Practices 

{

internal class ExceptionDemo2 

{

private static void Main(string[] args)

{// Create some random data to process in parallel.  

// There is a good probability this data will cause some exceptions to be thrown.  

byte[] data = new byte[5000];

Random r = new Random();

r.NextBytes(data);

try 

{

ProcessDataInParallel(data);

}

catch (AggregateException ae)

{

// This is where you can choose which ex ceptions to handle. 

foreach (var ex in ae.InnerExceptions)

{

if (ex is ArgumentException)

Console.WriteLine(ex.Message);

else 

throw ex;

}

}

Console.WriteLine("Press any key to exit.");

Console.ReadKey();

}

private static void ProcessDataInParallel(byte[] data)

{

// Use ConcurrentQueue to enable safe enqueueing from multiple threads.  

var exceptions = new ConcurrentQueue<Exception>();

// Execute the complete loop and capture all exceptions.  

Parallel.ForEach(data, d =>

{

try 

{

// Cause a few exceptions, but not too many.  

if (d < 0x3)

throw new ArgumentException(

String.Format("value is {0:x}. Elements must be greater than 0x3.", d));

else 

Console.Write(d + " ");

}

// Store the exception and continue with the loop.

catch (Exception e)

{

exceptions.Enqueue(e);

}

});

// Throw the exceptions here after the loop completes. 

if (exceptions.Count > 0) throw new AggregateException(exceptions);

}

}

}

Acelerar cuerpos de bucle pequeños

Cuando un bucle For() tiene un cuerpo pequeño, puede registrar un rendimiento más lento que el del bucle secuencial equivalente. Este rendimiento más lento

es consecuencia de la sobrecarga en la participación de los datos y el costo de invocar un delegado en cada iteración del bucle. Para hacer frente a estos

escenarios, la clase Partitioner proporciona el método Create, que permite proporcionar un bucle secuencial para el cuerpo de delegado de modo que el

delegado solo se invoque una vez por partición, en lugar de una vez por iteración.

using System;

using System.Collections.Concurrent;

using System.Linq;

using System.Threading.Tasks;

namespace Paralell_Practices 

Page 9: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 9/41

 

{

internal class Accelerate 

{

private static void Main()

{

// Source must be array or IList. 

var source = Enumerable.Range(0, 100000).ToArray();

// Partition the entire source array. 

var rangePartitioner = Partitioner.Create(0, source.Length);

double[] results = new double[source.Length];

// Loop over the partitions in parallel.  

Parallel.ForEach(rangePartitioner, (range, loopState) =>

{

// Loop over each range element without a delegate invocation. 

for (int i = range.Item1; i < range.Item2; i++)

{

results[i] = source[i]*Math.PI;

}

});

Console.WriteLine("Operation complete. Print results? y/n");

char input = Console.ReadKey().KeyChar;

if (input == 'y' || input == 'Y')

{

foreach (double d in results)

{

Console.Write("{0} ", d);

}

}

}

}

}

El enfoque mostrado en este ejemplo es útil cuando el bucle realiza una cantidad de trabajo mínima. Cuando el trabajo se vuelve más costoso en los cálculos,

obtendrá probablemente un rendimiento igual o mejor si usa un bucle For o ForEach con el particionador predeterminado.

Recorrer en iteración directorios con la clase paralela

En muchos casos, la iteración de archivo es una operación que se puede paralelizar fácilmente. El tema   Cómo: Recorrer en iteración directorios con PLIN

muestra la manera más fácil de realizar esta tarea en muchos escenarios. Sin embargo, pueden surgir complicaciones cuando el código tiene que tratar con lo

muchos tipos de excepciones que pueden surgir al obtener acceso al sistema de archivos. En el ejemplo siguiente se muestra un enfoque para el problema. Us

una iteración basada en la pila para recorrer todos los archivos y carpetas en un directorio especificado y habilita el código para detectar y controlar divers

excepciones. Por supuesto, la forma de controlar las excepciones depende de usted.

En el ejemplo siguiente la iteración en los directorios se realiza de forma secuencial, pero el procesamiento de los archivos se realiza en paralelo. Este enfoque

probablemente el mejor cuando hay una tasa alta de directorios y archivos. También es posible ejecutar la iteración de directorio y obtener acceso a cad

archivo secuencialmente. Probablemente no es eficaz paralelizar ambos bucles a menos que esté dirigido específicamente a un equipo con un gran número d

procesadores. Sin embargo, como en todos los casos, se debe probar exhaustivamente la aplicación para determinar el mejor enfoque.

using System;

using System.Collections.Generic;

using System.Diagnostics;

using System.IO;

using System.Threading;

using System.Threading.Tasks;

namespace Parallel_File 

{

internal class Program 

{

private static void Main(string[] args)

{

TraverseTreeParallelForEach( @"C:\Program Files", (f ) =>

{

// For this demo we don't do anything with the data 

// except to read it. 

byte[] data = File.ReadAllBytes(f );

// For user interest, although it slows down the operation.  

Console.WriteLine(f );

});

// Keep the console window open. 

Console.ReadKey();

}

public static void TraverseTreeParallelForEach(string root, Action<string> action)

{

//Count of files traversed and timer for diagnostic output 

int fileCount = 0;

var sw = Stopwatch.StartNew();

Page 10: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 10/41

 

  // Use this value to determine whether to parallelize  

// file processing on each folder. 

int procCount = System.Environment.ProcessorCount;

// Data structure to hold names of subfolders to be  

// examined for files. 

Stack<string> dirs = new Stack<string>();

if (!System.IO.Directory.Exists(root))

{

throw new ArgumentException();

}

dirs.Push(root);

while (dirs.Count > 0)

{

string currentDir = dirs.Pop();

string[] subDirs = null;

string[] files = null;

try 

{

subDirs = System.IO.Directory.GetDirectories(currentDir);

}

// An UnauthorizedAccessException exception will be thrown if we do not have  

// discovery permission on a folder or file. It may or may not be a cceptable

// to ignore the exception and continue enumerating the remaining files and

// folders. It is also possible (but unlikely) that a DirectoryNotFound exception

// will be raised. This will happen if currentDir has been deleted by 

// another application or thread after our call to Directory.Exists. The

// choice of which exceptions to catch depends entirely on the specific task

// you are intending to perform and also on how much you know with certainty

// about the systems on which this code will run.  

catch (UnauthorizedAccessException e)

{Console.WriteLine(e.Message);

continue;

}

catch (System.IO.DirectoryNotFoundException e)

{

Console.WriteLine(e.Message);

continue;

}

try 

{

files = System.IO.Directory.GetFiles(currentDir);

}

catch (UnauthorizedAccessException e)

{

Console.WriteLine(e.Message);

continue;

}

catch (System.IO.DirectoryNotFoundException e)

{

Console.WriteLine(e.Message);

continue;}

// Perform the required action on each file here in parallel if there are a sufficient number of files in the directory  

// or else sequentially if not. Files are opened and processed synchronously but this could be modified to perform async I/O. 

try 

{

if (files.Length < procCount)

{

foreach (var file in files)

{

action(file);

fileCount++;

}

}

else 

{

Parallel.ForEach(files, () => 0, (file, loopState, localCount) =>

{

action(file);

return (int) ++localCount;

},

(c) =>{

Interlocked.Exchange(ref  fileCount, fileCount + c);

});

}

}

catch (AggregateException ae)

{

ae.Handle((ex) =>

{

if (ex is UnauthorizedAccessException)

{

// Here we just output a message and go on.  

Console.WriteLine(ex.Message);

return true;

Page 11: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 11/41

 

}

// Handle other exceptions here if necessary...  

return false;

});

}

// Push the subdirectories onto the stack for traversal. This could a lso be done before handing the files.  

foreach (string str in subDirs)

dirs.Push(str);

}

// For diagnostic purposes. 

Console.WriteLine("Processed {0} files in {1} milleseconds", fileCount, sw.ElapsedMilliseconds);

}

}

}

En este ejemplo, la E/S de archivo se realiza de forma sincrónica. Al trabajar con archivos grandes o conexiones de red lentas, puede ser preferible obtener

acceso a los archivos de forma asincrónica. Puede combinar las técnicas de E/S asincrónica con la iteración paralela. Para obtener más información, vea  TPL y la

programación asincrónica tradicional de .NET. 

Tenga en cuenta que si se produce una excepción en el subproceso principal, los subprocesos que inicia el método  ForEach podrían seguir ejecutándose. Pa

detener estos subprocesos, puede establecer una variable booleana en los controladores de excepciones y comprobar su valor en cada iteración del buc

paralelo. Si el valor indica que se ha iniciado una excepción, use la variable  ParallelLoopState para detener o interrumpir el bucle.

la biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) se basa en el concepto de tarea ("task" en inglés). El términ

paralelismo de tareas hace referencia a la ejecución simultánea de una o varias tareas independientes. Una tarea representa una operación asincrónica y,

ciertos aspectos, se asemeja a la creación de un nuevo subproceso o elemento de trabajo ThreadPool, pero con un nivel de abstracción mayor. Las tarea

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 (como el algoritmo de ascenso d

colina o "hill-climbing") que determinan y ajustan el número de subprocesos con el que se maximiza el rendimiento. Esto hace que las tareas resulte

relativamente ligeras y que, por tanto, pueda crearse un gran número de ellas para habilitar un paralelismo pormenorizado. Como complemento

para proporcionar el equilibrio de carga, se usan los conocidos algoritmos de robo de trabajo.

  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.

El método Parallel.Invoke proporciona una manera conveniente de ejecutar cualquier número de instrucciones arbitrarias simultáneamente. Pase un delegad

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étod

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

vez.

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

Una tarea se representa mediante la clase System.Threading.Tasks.Task . Una tarea que devuelve un valor se representa mediante la cla

System.Threading.Tasks.Task<TResult> , que se hereda de Task. El objeto de tarea administra los detalles de la infraestructura y proporciona métodos

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 ten

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 h

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.

// Create a task and supply a user delegate by using a lambda expression.  

var taskA = new Task(() => Console.WriteLine("Hello from taskA."));

// Start the task. 

taskA.Start();

// Output a message from the joining thread. 

Console.WriteLine("Hello from the calling thread.");

Page 12: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 12/41

 

También se puede usar el método StartNew para crear e iniciar una tarea en una sola operación

// Create and start the task in one operation.  

var taskA = Task.Factory.StartNew(() => Console.WriteLine("Hello from taskA."));

// Output a message from the joining thread. 

Console.WriteLine("Hello from the joining thread.");

La tarea expone una propiedad Factory estática que devuelve una instancia predeterminada de   TaskFactory, por lo que se puede llamar al método com

Task.Factory.StartNew(…). Asimismo, en este ejemplo, dado que las tareas son de tipo  System.Threading.Tasks.Task<TResult> , cada una tiene una propiedResult 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

Result antes de que el cálculo se complete, la propiedad se bloqueará el subproceso hasta que el valor esté disponible.

Task<double>[] taskArray = new Task<double>[]

{

Task<double>.Factory.StartNew(() => DoComputation1()),

// May be written more conveniently like this: 

Task.Factory.StartNew(() => DoComputation2()),

Task.Factory.StartNew(() => DoComputation3())

};

double[] results = new double[taskArray.Length];

for (int i = 0; i < taskArray.Length; i++)

results[i] = taskArray[i].Result;

Cuando se usa una expresión lambda para crear el delegado de una tarea, se obtiene acceso a todas las variables que están visibles en ese momento en el códig

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, n

el valor tal y como se transforma después de cada iteración. Puede obtener acceso al valor en cada iteración si proporciona un objeto de estado a una tarea

través de su constructor

class MyCustomData

{

public long CreationTime;

public int Name;

public int ThreadNum;

}

void TaskDemo2(){

// Create the task object by using an Action(Of Object) to pass in custom data  

// in the Task constructor. This is useful when you need to capture outer variables  

// from within a loop. As an experiement, try modifying this code to

// capture i directly in the lambda, and compare results.  

Task[] taskArray = new Task[10];

for(int i = 0; i < taskArray.Length; i++)

{

taskArray[i] = new Task((obj) =>

{

MyCustomData mydata = (MyCustomData) obj;

mydata.ThreadNum = Thread.CurrentThread.ManagedThreadId;

Console.WriteLine("Hello from Task #{0} created at {1} running on thread #{2}.",

mydata.Name, mydata.CreationTime, mydata.ThreadNum)

},

new MyCustomData () {Name = i, CreationTime = DateTime.Now.Ticks}

);

taskArray[i].Start();

}

}

Este estado se pasa como argumento al delegado de la tarea y es accesible desde el objeto de tarea mediante la propiedad  AsyncState. Además, el paso de los

datos a través del constructor podría proporcionar una pequeña ventaja de rendimiento en algunos escenarios.

Page 13: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 13/41

 

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

propiedad Id. El identificador resulta útil para ver información sobre la tarea en las ventanas Pilas paralelas y Tareas paralelas del depurador de Visual Studio.

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 ve

que se ejecute el programa.

La mayoría de las API que crean tareas proporcionan sobrecargas que aceptan un parámetro  TaskCreationOptions. Al especificar una de estas opciones, se

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 d

tareas.

Elemento 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.

var task3 = new Task(() => MyLongRunningMethod(),

TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);

task3.Start();

El método Task.ContinueWith y el método Task<TResult>.ContinueWith  permiten especificar que una tarea se inicie cuando la tarea anterior se complete.

delegado de la tarea de continuación se le pasa una referencia de la tarea anterior para que pueda examinar su estado. Además, la tarea de continuación pued

recibir de la tarea anterior un valor definido por el usuario en la propiedad Result para que la salida de la tarea anterior pueda servir de entrada de la tarea d

continuación. En el ejemplo siguiente, el código del programa inicia getData; a continuación, se inicia analyzeData automáticamente cuando getData

completa; por último, reportData se inicia cuando analyzeData se completa. getData genera como resultado una matriz de bytes, que se pasa a analyzeDatanalyzeData procesa esa matriz y devuelve un resultado cuyo tipo se infiere del tipo devuelto del método Analyze. reportData toma la entrada de analyzeData

genera un resultado cuyo tipo se infiere de forma similar y que se pasa a estar disponible en el programa en la propiedad  Result. 

Task<byte[]> getData = new Task<byte[]>(() => GetFileData());

Task<double[]> analyzeData = getData.ContinueWith(x => Analyze(x.Result));

Task<string> reportData = analyzeData.ContinueWith(y => Summarize(y.Result));

getData.Start();

//or... 

Task<string> reportData2 = Task.Factory.StartNew(() => GetFileData())

.ContinueWith((x) => Analyze(x.Result))

.ContinueWith((y) => Summarize(y.Result));

System.IO.File.WriteAllText( @"C:\reportFolder\report.txt", reportData.Result);

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

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 externa de ninguna manera especial. Este tipo de tareas se denominan tareas anidadas desasociadas.

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.");

/* Output:

Outer task beginning.

Outer task completed.

Detached task completed.

Page 14: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 14/41

 

 

*/ 

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 concibe como una tar

secundaria de la tarea original, que se denomina tarea primaria. Puede usar la opción AttachedToParent para expresar el paralelismo de tareas estructurado,

que la tarea primaria espera implícitamente a que todas las tareas secundarias se completen.

var parent = Task.Factory.StartNew(() =>

{

Console.WriteLine("Parent task beginning.");

var child = Task.Factory.StartNew(() =>

{

Thread.SpinWait(5000000);

Console.WriteLine("Attached child completed.");

}, TaskCreationOptions.AttachedToParent);

});

parent.Wait();

Console.WriteLine("Parent task completed.");

/* Output:

Parent task beginning.

Attached task completed.

Parent task completed.

*/ 

El tipo System.Threading.Tasks.Task  y el tipo System.Threading.Tasks.Task<TResult>  proporcionan varias sobrecargas de un método Task.Wait y

Task<TResult>.Wait  que permiten esperar a que una tarea se complete. Además, las sobrecargas del método  Task.WaitAll  estático y del método Task.WaitAny 

permiten esperar a que se complete 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.

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

Algunas sobrecargas permiten especificar un tiempo de espera, mientras que otras toman un objeto CancellationToken adicional como parámetro de entrada, d

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  AttachedToParent  

TaskCreationOptions. Task.Wait devuelve un valor inmediatamente si la tarea ya se ha completado. Un método  Wait producirá las tareas generadas por u

tarea incluso si se llama a este método Wait una vez completada la tarea.

Cuando una tarea produce una o varias excepciones, las excepciones se encapsulan en un objeto AggregateException. Esa excepción se propaga de nuevo

subproceso que se combina con la tarea, que normalmente es el subproceso que está esperando a la tarea o que intenta tener acceso a la propiedad Result de

tarea. Este comportamiento sirve para aplicar la directiva de .NET Framework por la que, de manera predeterminada, todas las excepciones no controlad

deben anular el proceso. El código de llamada puede controlar las excepciones a través de los métodos Wait, WaitAll o WaitAny o de la propiedad Result() de

tarea o grupo de tareas, mientras incluye el método Wait en un bloque try-catch.

Page 15: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 15/41

 

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 com

elemento no utilizado. Al obtener acceso a esta propiedad, impide que la excepción no controlada desencadene el comportamiento de propagación de

excepción que anula el proceso cuando el objeto ha finalizado.

La clase Task admite la cancelación cooperativa y su completa integración con las clases System.Threading.CancellationTokenSource  y

System.Threading.CancellationToken , que son nuevas en .NET Framework versión 4. Muchos de los constructores de la clase  System.Threading.Tasks.Task  toma

un objeto CancellationToken como parámetro de entrada. Muchas de las sobrecargas de StartNew toman también 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.

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.

  El modelo más común es StartNew, que crea e inicia una tarea en una sola instrucción. Para obtener más información, vea StartNew().

  Cuando cree tareas de continuación a partir de varios antecedentes, use el método  ContinueWhenAll o el métodoContinueWhenAnyo sus

equivalentes en la clase Task<TResult>. Para obtener más información, vea Tareas de continuación.

  Para encapsular los métodos BeginX y EndX del modelo de programación asincrónica en una instancia de Task o Task<TResult>, use los métodos

FromAsync. Para obtener más información, vea TPL y la programación asincrónica tradicional de .NET. 

El objeto TaskFactory predeterminado es accesible como propiedad estática de la clase  Task o de la clase Task<TResult>. También pueden crearse directamen

instancias de TaskFactory y especificar varias opciones entre las que se incluyan las opciones  CancellationToken, TaskCreationOptions, TaskContinuationOptio

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 la tar

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

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

este el caso, puede usar el objeto  TaskCompletionSource<TResult>   para encapsular la operación en una tarea y, de este modo, aprovechar algunas de l

ventajas de programación de Task, como por ejemplo, su compatibilidad con la propagación de excepciones y el uso de continuaciones.

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 tare

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

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 Librar

biblioteca de procesamiento paralelo basado en tareas) permite configurar algunos valores del programador de tareas predeterminado e incluso permi

proporcionar un programador personalizado.

TPL tiene varios tipos públicos nuevos que resultan útiles tanto en escenarios en paralelo como en escenarios secuenciales. Entre ellos, se incluyen diversa

clases de colecciones multiproceso rápidas y escalables del espacio de nombres  System.Collections.Concurrent  y varios tipos nuevos de sincronización, comSemaphoreLock y System.Threading.ManualResetEventSlim , que resultan más eficaces que sus predecesores en tipos concretos de cargas de trabajo. Otros tip

nuevos de .NET Framework versión 4, como System.Threading.Barrier  y System.Threading.SpinLock , proporcionan una funcionalidad que no estaba disponible e

versiones anteriores.

Se recomienda no heredar de System.Threading.Tasks.Task  ni de System.Threading.Tasks.Task<TResult> . En su lugar, use la propiedad AsyncState para asoc

los datos adicionales o el estado con un objeto Task o Task<TResult>. También puede usar métodos de extensión para extender la funcionalidad de las clas

Task y Task<TResult>.

i debe heredar de Task  o Task<TResult>, no puede usar las clases System.Threading.Tasks.TaskFactory ,  System.Threading.Tasks.TaskFactory<TResult>  

System.Threading.Tasks.TaskCompletionSource<TResult>   para crear instancias del tipo de tarea personalizada porque estas clases solo crean objetos  Task

Task<TResult>. Además, no puede usar los mecanismos de continuación de tarea proporcionados por  Task, Task<TResult>, TaskFactory y TaskFactory<TResul

para crear instancias del tipo de tarea personalizada porque también estos mecanismos crean solo objetos  Task y Task<TResult>. 

En la programación asincrónica, es muy común que una operación asincrónica, cuando se completa, invoque una segunda operación y le pase datoTradicionalmente, esto se hacía utilizando métodos de devolución de llamada. En la biblioteca TPL (Task Parallel Library, biblioteca de procesamiento parale

basado en tareas), las tareas de continuación proporcionan la misma funcionalidad. Una tarea de continuación (o, simplemente, una continuación) es una tar

asincrónica invocada por otra tarea, que se denomina antecedente, cuando se completa el antecedente.

Las continuaciones son relativamente fáciles de usar, y no por ello dejan de ser eficaces y flexibles. Por ejemplo, puede:

  Pasar datos del antecedente a la continuación

  Especificar las condiciones precisas en las que se invocará o no la continuación

  Cancelar una continuación antes de iniciarse o, de manera cooperativa, mientras se está ejecutando

Page 16: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 16/41

 

  Proporcionar sugerencias sobre cómo se debería programar la continuación

  Invocar varias continuaciones desde el mismo antecedente

  Invocar una continuación cuando se completa una parte o la totalidad de los antecedentes

  Encadenar las continuaciones una tras otra hasta cualquier longitud arbitraria

  Usar una continuación para controlar las excepciones producidas por el antecedente

Las continuaciones se crean con el método Task.ContinueWith. En el siguiente ejemplo se muestra el modelo básico (para mayor claridad, se omite el control de

excepciones).

// The antecedent task. Can also be created with Task.Factory.StartNew.  

Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);

// The continuation. Its delegate takes the antecedent task 

// as an argument and can return a different type.  

Task<string> continuation = taskA.ContinueWith((antecedent) =>

{

return String.Format("Today is {0}.",

antecedent.Result);

});

// Start the antecedent. 

taskA.Start();

// Use the contuation's result. 

Console.WriteLine(continuation.Result);

También puede crear una continuación de varias tareas que se ejecutará cuando una parte o la totalidad de las tareas de una matriz de tareas se haya

completado

Task<int>[] tasks = new Task<int>[2];

tasks[0] = new Task<int>(() =>

{

// Do some work...

return 34;

});

tasks[1] = new Task<int>(() =>

{

// Do some work... 

return 8;});

var continuation = Task.Factory.ContinueWhenAll(

tasks,

(antecedents) =>

{

int answer = tasks[0].Result + tasks[1].Result;

Console.WriteLine("The answer is {0}", answer);

});

tasks[0].Start();

tasks[1].Start();

continuation.Wait();

Una continuación se crea en el estado WaitingForActivation y, por lo tanto, únicamente puede iniciarla su tarea antecedente. Al llamar a Task.Start en una

continuación en el código de usuario, se produce una excepción System.InvalidOperationException .

Una continuación es un objeto Task y no bloquea el subproceso en el que se inicia. Use el método Wait para bloquearlo hasta que la tarea de continuación se

complete.

Elemento Descripción

 

None  Especifica el comportamiento predeterminado cuando no se especifican TaskContinuationOptions . La continuación se programará

una vez completado el antecedente, independientemente del estado final de este. Si la tarea es una tarea secundaria, se crea como

Page 17: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 17/41

 

una tarea anidada desasociada.

PreferFairness  Especifica que la continuación se programará de modo que las tareas programadas antes tengan más posibilidades de ejecutarse

antes y las tareas programadas después tengan más posibilidades de ejecutarse más tarde.

LongRunning  Especifica que la continuación será una operación general de larga duración. Proporciona una sugerencia al

System.Threading.Tasks.TaskScheduler  de que se puede garantizar la sobresuscripción.

AttachedToParent  Especifica que la continuación, si es una tarea secundaria, se adjunta a un elemento primario en la jerarquía de tareas. La

continuación es una tarea secundaria solo si su antecedente también es una tarea secundaria.

 

NotOnRanToCompletion  Especifica que no se debe programar la continuación si su antecedente se ejecuta completamente.

NotOnFaulted  Especifica que no se debe programar la continuación si su antecedente produjo una excepción no controlada.

NotOnCanceled  Especifica que no se debe programar la continuación si se cancela su antecedente.

OnlyOnRanToCompletion  Especifica que la continuación solo se debe programar si el antecedente se ejecuta completamente.OnlyOnFaulted 

 

Especifica que la continuación solo se debe programar si su antecedente produjo una excepción no controlada. Al usar la opción

OnlyOnFaulted, se garantiza que la propiedad Exception del antecedente no es NULL. Puede usar esa propiedad para detectar la

excepción y ver qué excepción provocó el error de la tarea. Si no tiene acceso a la propiedad  Exception, no se controlará la

excepción. Asimismo, si intenta tener acceso a la propiedad Result de una tarea cancelada o con errores, se producirá una nueva

excepción.

OnlyOnCanceled  Especifica que la continuación debe programarse únicamente si su antecedente se completa en estado  Canceled. 

 

ExecuteSynchronously  Para las continuaciones de muy corta duración. Especifica que lo ideal es que la continuación se ejecute en el mismo subproceso

que causa la transición del antecedente a su estado final. Si el antecedente ya se ha completado cuando se crea la continuación, el

sistema intentará ejecutar la continuación en el subproceso que la crea. Si se elimina  CancellationTokenSource del antecedente en

un bloque finally (Finally en Visual Basic), se ejecutará una continuación con esta opción en ese bloque finally.

Una referencia al antecedente se pasa como argumento al delegado de usuario de la continuación. Si el antecedente es  System.Threading.Tasks.Task<TResult>

la tarea se ejecutó completamente, la continuación puede tener acceso a la propiedad Task<TResult>.Result  de la tarea. Con una continuación de varias tareas

el método Task.WaitAll, el argumento es la matriz de antecedentes. Al usar Task.WaitAny, el argumento es el primer antecedente que se completó.

Task<TResult>.Result  se bloquea hasta que la tarea se ha completado. Sin embargo, si la tarea se canceló o tiene errores , Result produce una excepción cuando

el código intenta tener acceso al mismo. Puede evitar este problema mediante la opción  OnlyOnRanToCompletion 

var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>

{

Console.WriteLine("continuation {0}", antecedent.Result);

},

TaskContinuationOptions.OnlyOnRanToCompletion);

Si desea que la continuación se ejecute aunque el antecedente no se ejecute completamente, debe usar medidas de protección contra la excepción. Un posib

enfoque es probar el estado del antecedente e intentar tener acceso a Result  solamente si el estado no es  Faulted o Canceled. También puede examinar propiedad Exception del antecedente.

Una continuación pasa al estado Canceled en estos escenarios:

  Cuando produce una excepción OperationCanceledException  en respuesta a una solicitud de cancelación. Al igual que sucede con cualquier tarea, si

excepción contiene el mismo token que se pasó a la continuación, se trata como una confirmación de cancelación cooperativa.

  Cuando se pasa System.Threading.CancellationToken  como argumento a la continuación y la propiedad IsCancellationRequested del token es true

(True) antes de ejecutar la continuación. En este caso, la continuación no se inicia y pasa directamente al estado  Canceled. 

  Cuando la continuación nunca se ejecuta porque no se cumple la condición establecida en TaskContinuationOptions . Por ejemplo, si una tarea entra

en estado Faulted, su continuación, creada con la opción NotOnFaulted, pasará al estado Canceled y no se ejecutará.

Para que una continuación no se ejecute si su antecedente se cancela, especifique la opción  NotOnCanceled al crear la continuación.

Si una tarea y su continuación representan dos partes de la misma operación lógica, puede pasar el mismo token de cancelación a ambas tareas

Task task = new Task(() =>

{

CancellationToken ct = cts.Token;

while (someCondition)

{

ct.ThrowIfCancellationRequested();

// Do the work. 

//...

Page 18: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 18/41

 

}

},

cts.Token

);

Task task2 = task.ContinueWith((antecedent) =>

{

CancellationToken ct = cts.Token;

while (someCondition)

{ct.ThrowIfCancellationRequested();

// Do the work. 

//...

}

},

cts.Token);

task.Start();

//... 

// Antecedent and/or continuation will

// respond to this request, depending on when it is made.  

cts.Cancel();

Si el antecedente no se cancela, todavía se puede usar el token para cancelar la continuación. Si el antecedente se cancela, no se inicia la continuación.

Después de que una continuación entra en estado Canceled, puede afectar a las continuaciones posteriores, dependiendo de las opciones

TaskContinuationOptions especificadas para esas continuaciones.

Las continuaciones eliminadas no se inician.

Una continuación no se ejecuta hasta que se completan su antecedente y todas las tareas secundarias asociadas. La continuación no espera a que se complet

las tareas secundarias desasociadas. El estado final de la tarea antecedente depende del estado final de cualquier tarea secundaria asociada. El estado de la

tareas secundarias desasociadas no afecta a la tarea primaria.

Una relación entre un antecedente y una continuación no es una relación primario-secundario. Las excepciones producidas por las continuaciones no

propagan al antecedente. Por consiguiente, las excepciones que producen las continuaciones se deben controlar de igual modo que en cualquier otra tare

como se indica a continuación.

1.  Use el método Wait, WaitAny o WaitAll, o su homólogo genérico, para esperar en la continuación. Puede esperar a un antecedente y sus

continuaciones en la misma instrucción try

var t = Task<int>.Factory.StartNew(() => 54);

var c = t.ContinueWith((antecedent) =>

{

Console.WriteLine("continuation {0}", antecedent.Result);

throw new InvalidOperationException();

});

try 

{t.Wait();

c.Wait();

}

catch (AggregateException ae)

{

foreach(var e in ae.InnerExceptions)

Console.WriteLine(e.Message);

}

Console.WriteLine("Exception handled. Let's move on.");

Page 19: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 19/41

 

Use una segunda continuación para observar la propiedad Exception de la primera continuación.

Si la continuación es una tarea secundaria creada mediante la opción AttachedToParent, la tarea primaria propagará sus excepciones al subproceso que reali

la llamada, como sucede con cualquier otro elemento secundario asociado.

Una tarea anidada no es más que una instancia de Task que se crea en el delegado de usuario de otra tarea. Una tarea secundaria es una tarea anidada que s

crea con la opción  AttachedToParent. Una tarea puede crear cualquier número de tareas secundarias y anidadas, con la única limitación de los recursos d

sistema.

static void SimpleNestedTask()

{

var parent = Task.Factory.StartNew(() =>

{

Console.WriteLine("Outer task executing.");

var child = Task.Factory.StartNew(() =>

{

Console.WriteLine("Nested task starting.");

Thread.SpinWait(500000);

Console.WriteLine("Nested task completing.");

});

});

parent.Wait();

Console.WriteLine("Outer has completed.");}

/* Sample output:

Outer task executing.

Nested task starting.

Outer has completed.

Nested task completing.

*/ 

El aspecto más importante en lo que se refiere a las tareas secundarias y anidadas es que las tareas anidadas son esencialmente independientes de la tarea

primaria o externa, mientras que las tareas secundarias asociadas están estrechamente sincronizadas con el la tarea primaria. Si se modifica la instrucción de

creación de la tarea para usar la opción AttachedToParent 

var child = Task.Factory.StartNew((t) =>

{

Console.WriteLine("Attached child starting.");

Thread.SpinWait(5000000);

Console.WriteLine("Attached child completing.");

}, TaskCreationOptions.AttachedToParent);

/* Sample output:

Parent task executing.

Attached child starting.

Attached child completing.

Parent has completed.

*/ 

Puede usar tareas secundarias asociadas para crear gráficos de operaciones asincrónicas con una estrecha sincronización. Sin embargo, en la mayoría de l

escenarios, recomendamos usar tareas anidadas porque las relaciones con otras tareas son menos complejas. Esta es la razón por la que las tareas que se crea

dentro de otras tareas están anidadas de forma predeterminada y es necesario especificar explícitamente la opción AttachedToParent para crear una taresecundaria.

En la tabla siguiente se muestran las diferencias básicas entre los dos tipos de tareas secundarias.

Categoría Tareas anidadas Tareas secundarias asociadas

La tarea externa (primaria) espera a que las tareas internas se completen. No Sí 

La tarea primaria propaga las excepciones iniciadas por las tareas secundarias (tareas internas). No Sí 

El estado de la tarea primaria (tarea externa) depende del estado de la tarea secundaria (tarea interna). No Sí 

Page 20: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 20/41

 

En escenarios desasociados en los que la tarea anidada es un objetoTask<TResult>, se puede forzar que la tarea primaria espere a la secundaria mediante el

acceso a la propiedad Result de la tarea anidada. La propiedad Result se bloquea hasta que su tarea se completa.

static void WaitForSimpleNestedTask()

{

var outer = Task<int>.Factory.StartNew(() =>

{

Console.WriteLine("Outer task executing.");

var nested = Task<int>.Factory.StartNew(() =>

{

Console.WriteLine("Nested task starting.");

Thread.SpinWait(5000000);

Console.WriteLine("Nested task completing.");

return 42;

});

// Parent will wait for this detached child. 

return nested.Result;

});

Console.WriteLine("Outer has returned {0}.", outer.Result);

}

/* Sample output:

Outer task executing.Nested task starting.

Nested task completing.

Outer has returned 42.

*/ 

Si una tarea anidada produce una excepción, debe observarse o controlarse directamente en la tarea exterior como si se tratara de una tarea no anidada. Si un

tarea secundaria adjunta inicia una excepción, la excepción se propaga automáticamente a la tarea primaria y de nuevo al subproceso, que espera o inten

obtener acceso a la propiedad Result de la tarea. Por tanto, si se usan tareas secundarias asociadas, se pueden controlar todas las excepciones en un solo punt

la llamada a Wait del subproceso que realiza la llamada.

No conviene olvidar que la cancelación de tareas es cooperativa. Por tanto, para ser "cancelable", cada tarea secundaria asociada o desasociada debe supervis

el estado del token de cancelación. Si desea cancelar un elemento primario y todos sus elementos secundarios utilizando una sola solicitud de cancelación, deb

pasar el mismo token como argumento a todas las tareas y proporcionar en cada tarea la lógica de respuesta a la solicitud.

Si una tarea primaria se cancela antes de que se inicie una tarea secundaria, como es lógico, la tarea secundaria (anidada) nunca se cancelará. Si una tare

primaria se cancela después de que se ha iniciado una tarea secundaria o anidada, la tarea anidada (secundaria) se ejecutará hasta completarse a menos q

tenga su propia lógica de cancelación.

Si una tarea secundaria desasociada se cancela usando el mismo token que se pasó a la tarea y la tarea primaria no espera a la secundaria, no se propagar

ninguna excepción, pues la excepción se trata cono una cancelación de cooperación benigna. Este comportamiento es igual que el de cualquier tarea de niv

superior.

Cuando una tarea secundaria asociada se cancela usando el mismo token que se pasó a la tarea, se propaga una excepción  TaskCanceledException al subproce

de unión dentro de AggregateException . Es muy importante esperar a la tarea primaria para poder controlar todas las excepciones benignas además de todos l

excepciones de error que se propagan de manera ascendente a través de un gráfico de tareas secundarias asociadas.

Cancelación de tareas

Las clases System.Threading.Tasks.Task<TResult>  y System.Threading.Tasks.Task  admiten la cancelación a través del uso de tokens de cancelación, que son u

novedad de .NET Framework 4. Para obtener más información, vea Cancelación. En las clases de tareas, la cancelación implica la cooperación entre el delegad

de usuario, que representa una operación cancelable y el código que solicitó la cancelación. Una cancelación correcta implica que el código solicitante llame

método CancellationTokenSource.Cancel  y que el delegado de usuario termine la operación en el tiempo esperado. Puede finalizar la operación a través de un

de estas opciones:

Page 21: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 21/41

 

  Devolver simplemente un valor del delegado. En muchos escenarios esto es suficiente; sin embargo, una instancia de tarea "cancelada" de esta

manera cambia al estado RanToCompletion, no al estado Canceled.

  Producir una excepción OperationCanceledException y pasarle el token en el que se solicitó la cancelación. En este caso, se prefiere usar el métod

ThrowIfCancellationRequested. Una tarea cancelada de esta manera cambia al estado Canceled, que sirve al código que realiza la llamada pa

comprobar que la tarea respondió a su solicitud de cancelación.

using System;

using System.Threading;

using System.Threading.Tasks;

internal class Cancel_Task {

private static void Main()

{

var tokenSource2 = new CancellationTokenSource();

CancellationToken ct = tokenSource2.Token;

var task = Task.Factory.StartNew(() =>

{

// Were we already canceled? 

ct.ThrowIfCancellationRequested();

bool moreToDo = true;

while (moreToDo)

{

// Poll on this property if you have to do  

// other cleanup before throwing. 

if (ct.IsCancellationRequested)

{

// Clean up here, then... 

ct.ThrowIfCancellationRequested();}

}

}, tokenSource2.Token); // Pass same token to StartNew. 

tokenSource2.Cancel();

// Just continue on this thread, or Wait/WaitAll with try-catch: 

try 

{

task.Wait();

}

catch (AggregateException e)

{

foreach (var v in e.InnerExceptions)

Console.WriteLine(e.Message + " " + v.Message);

}

Console.ReadKey();

}

}

Cuando una instancia de tarea observa una excepción OperationCanceledException  iniciada desde el código de usuario, compara el token de la excepción con

token asociado (el que se pasó a la API que creó la tarea). Si son iguales y la propiedad IsCancellationRequested del token devuelve true, la tarea lo interpre

como una confirmación de cancelación y pasa al estado Canceled. Si no se usa un método  WaitAll o Wait para esperar a la tarea, esta simplemente establece

estado en Canceled.

Si espera en una tarea que cambia al estado Canceled, se crea y se inicia una excepción TaskCanceledException (encapsulada en  AggregateException). Obser

que esta excepción indica la cancelación correcta en lugar de una situación de error. Por consiguiente, la propiedad Exception de la tarea devuelve Null.

Si la propiedad IsCancellationRequested del token devuelve False o si el token de la excepción no coincide con el token de la tarea,  OperationCanceledExceptio

se trata como una excepción normal, por lo que la tarea cambia al estado Faulted. Observe también que la presencia de otras excepciones también hará que

tarea pase al estado Faulted. Puede obtener el estado de la tarea completada en la propiedad  Status. 

Es posible que una tarea continúe procesando algunos elementos una vez solicitada la cancelación.

Control de excepciones (Task Parallel Library)

Las excepciones no controladas que se inician mediante el código de usuario que se ejecuta dentro de una tarea se propagan de nuevo al subproceso de unió

excepto en determinados escenarios que se describen posteriormente en este tema. Las excepciones se propagan cuando se usa uno de los métodos estáticos

de instancia Task.Wait o Task<TResult>.Wait , y estos métodos se controlan si la llamada se enmarca en una instrucción try-catch. Si una tarea es la tarea primar

de unas tareas secundarias asociadas o si se esperan varias tareas, pueden producirse varias excepciones. Para propagar todas las excepciones de nuevo

subproceso que realiza la llamada, la infraestructura de la tarea las encapsula en una instancia de AggregateException . AggregateException  tiene una propied

InnerExceptions  que se puede enumerar para examinar todas las excepciones originales que se generaron y controlar (o no) cada una de ellas de form

individual. Aunque solo se inicie una única excepción, se encapsulará en un objeto  AggregateException . 

Page 22: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 22/41

 

var task1 = Task.Factory.StartNew(() =>

{

throw new MyCustomException("I'm bad, but not too bad!");

});

try 

{

task1.Wait();

}

catch (AggregateException ae)

{// Assume we know what's going on with this particular exception. 

// Rethrow anything else. AggregateException.Handle provides 

// another way to express this. See later example. 

foreach (var e in ae.InnerExceptions)

{

if (e is MyCustomException)

{

Console.WriteLine(e.Message);

}

else 

{

throw;

}

}

}

Para evitar una excepción no controlada, basta con detectar el objeto  AggregateException  y omitir las excepciones internas. Sin embargo, esta operación no

resulta recomendable porque es igual que detectar el tipo Exception base en escenarios no paralelos. Si desea detectar una excepción sin realizar acciones

concretas que la resuelvan, puede dejar al programa en un estado indeterminado.

Si no espera que ninguna tarea propague la excepción ni tiene acceso a su propiedad Exception, la excepción se escalará conforme a la directiva de excepciones

de .NET cuando la tarea se recopile como elemento no utilizado.

Cuando las excepciones pueden propagarse de nuevo al subproceso de unión, es posible que una tarea continúe procesando algunos elementos después de qu

se haya producido la excepción.

Si una tarea tiene una tarea secundaria adjunta que inicia una excepción, esa excepción se encapsula en un objeto  AggregateException  antes de que se propag

a la tarea primaria, que encapsula esa excepción en su propio objeto AggregateException  antes de propagarla de nuevo al subproceso que realiza la llamada.

casos como este, la propiedad AggregateException().InnerExceptions del objeto AggregateException   que se detecta en el método Task.Wa

Task<TResult>.Wait , WaitAny o WaitAll contiene una o varias instancias de AggregateException , pero no las excepciones originales que produjeron el error.

desea evitar tener que iterar en los objetos anidados AggregateExceptions, puede usar el método Flatten() para quitar todos los objetos anidad

AggregateExceptions de forma que la propiedad AggregateException() InnerExceptions contenga las excepciones originales.

// task1 will throw an AE inside an AE inside an AE 

var task1 = Task.Factory.StartNew(() =>

{

var child1 = Task.Factory.StartNew(() =>

{

var child2 = Task.Factory.StartNew(() =>

{

throw new MyCustomException("Attached child2 faulted.");

},

TaskCreationOptions.AttachedToParent);

// Uncomment this line to see the exception rethrown.  

// throw new MyCustomException("Attached child1 faulted.");  

},

TaskCreationOptions.AttachedToParent);

});

try 

{

Page 23: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 23/41

 

task1.Wait();

}

catch (AggregateException ae)

{

foreach (var e in ae.Flatten().InnerExceptions)

{

if (e is MyCustomException)

{

// Recover from the exception. Here we just 

// print the message for demonstration purposes. Console.WriteLine(e.Message);

}

else 

{

throw;

}

}

// or ... 

// ae.Flatten().Handle((ex) => ex is MyCustomException); 

}

De forma predeterminada, las tareas secundarias están desasociadas cuando se crean. Las excepciones producidas por tareas desasociadas deben controlarse

reiniciarse en la tarea primaria inmediata; no se propagan de nuevo al subproceso que realiza la llamada del mismo modo que las tareas secundarias asociadaLa tarea primaria superior puede reiniciar manualmente una excepción de una tarea desasociada para encapsularla en un objeto AggregateException

propagarla de nuevo al subproceso de unión.

var task1 = Task.Factory.StartNew(() =>

{

var nested1 = Task.Factory.StartNew(() =>

{

throw new MyCustomException("Nested task faulted.");

});

// Here the exception will be escalated back to joining thread. 

// We could use try/catch here to prevent that.  

nested1.Wait();

});

try 

{

task1.Wait();

}

catch (AggregateException ae)

{

foreach (var e in ae.Flatten().InnerExceptions)

{

if (e is MyCustomException)

{

// Recover from the exception. Here we just 

// print the message for demonstration purposes. 

Console.WriteLine(e.Message);}

}

}

Aunque se use una tarea de continuación para observar una excepción en una tarea secundaria, la tarea primaria debe seguir observando la excepción.

Cuando el código de usuario de una tarea responde a una solicitud de cancelación, el procedimiento correcto es producir una excepci

OperationCanceledException  que se pasa en el token de cancelación con el que se comunicó la solicitud. Antes de intentar propagar la excepción, la instancia d

la tarea compara el token de la excepción con el que recibió durante su creación. Si son iguales, la tarea propaga una excepción  TaskCanceledExceptio

Page 24: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 24/41

 

encapsulada en un elemento AggregateException  y puede verse cuando se examinan las excepciones internas. Sin embargo, si el subproceso de unión no es

esperando la tarea, no se propagará esta excepción concreta.

var tokenSource = new CancellationTokenSource();

var token = tokenSource.Token;

var task1 = Task.Factory.StartNew(() =>

{

CancellationToken ct = token;

while (someCondition)

{

// Do some work... 

Thread.SpinWait(50000);

ct.ThrowIfCancellationRequested();

}

},

token);

// No waiting required. 

El método Handle() puede usarse para filtrar excepciones que pueden tratarse como "controladas" sin necesidad de usar ninguna otra lógica. En el delegado

usuario que se proporciona a Handle(), se puede examinar el tipo de excepción, su propiedad Message() o cualquier otra información sobre esta excepción q

permita determinar si es benigna. Las excepciones en las que el delegado devuelve false se reinician inmediatamente en una nueva instancia

AggregateException después de que Handle() devuelve un valor.

En el siguiente fragmento de código se usa un bucle foreach sobre las excepciones internas.

foreach (var e in ae.InnerExceptions)

{

if (e is MyCustomException)

{

Console.WriteLine(e.Message);

}

else 

{

throw;

}

}

En el siguiente fragmento de código se muestra el uso del método Handle() con la misma función.

ae.Handle((ex) =>

{

return ex is MyCustomException;

});

Si una tarea se completa con el estado Faulted, se puede examinar su propiedad Exception para detectar qué excepción concreta produjo el error. Un

mecanismo adecuado para observar la propiedad Exception es usar una continuación que se ejecute solo si se produce un error en la tarea anterior  

var task1 = Task.Factory.StartNew(() =>{

throw new MyCustomException("Task1 faulted.");

})

.ContinueWith((t) =>

{

Console.WriteLine("I have observed a {0}",

t.Exception.InnerException.GetType().Name);

},

TaskContinuationOptions.OnlyOnFaulted);

Page 25: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 25/41

 

En una aplicación real, el delegado de continuación podría registrar información detallada sobre la excepción y posiblemente generar nuevas tareas para

recuperarse de la excepción.

En algunos escenarios (por ejemplo, cuando se hospedan complementos que no son de confianza), es posible que se produzcan numerosas excepcione

benignas y que resulte demasiado difícil observarlas todas manualmente. En estos casos, se puede proceder a controlar el even

TaskScheduler.UnobservedTaskException . La instancia de System.Threading.Tasks.UnobservedTaskExceptionEventArgs   que se pasa al controlador se pue

utilizar para evitar que la excepción no observada se propague de nuevo al subproceso de unión.

Devolver un valor de una tarea

using System;

using System.Linq;

using System.Threading.Tasks;

namespace Paralell_Practices 

{

internal class ReturnValue_Task 

{

private static void Main()

{

// Return a value type with a lambda expression 

Task<int> task1 = Task<int>.Factory.StartNew(() => 1);

int i = task1.Result;

// Return a named reference type with a multi -line statement lambda. 

Task<Test> task2 = Task<Test>.Factory.StartNew(() =>

{

string s = ".NET";

double d = 4.0;

return new Test {Name = s, Number = d};

});Test test = task2.Result;

// Return an array produced by a PLINQ query  

Task<string[]> task3 = Task<string[]>.Factory.StartNew(() =>

{

string path = @"C:\users\public\pictures\";

string[] files = System.IO.Directory.GetFiles(path);

var result = (from file in files.AsParallel()

let info = new System.IO.FileInfo(file)

where info.Extension == ".jpg" 

select file).ToArray();

return result;

});

foreach (var name in task3.Result)

Console.WriteLine(name);

}

private class Test 

{

public string Name { get; set; }

public double Number { get; set; }

}

}

}

La propiedad Result bloquea el subproceso que realiza la llamada hasta que la tarea finaliza.

Esperar a que una o varias tareas se completen

using System;

using System.Threading;

using System.Threading.Tasks;

namespace Paralell_Practices 

{

internal class WaitFinally_Task 

{

private static Random rand = new Random();

private static void Main(string[] args){

// Wait on a single task with no timeout specified.  

Task taskA = Task.Factory.StartNew(() => DoSomeWork(10000000));

taskA.Wait();

Console.WriteLine("taskA has completed.");

// Wait on a single task with a timeout specified.  

Task taskB = Task.Factory.StartNew(() => DoSomeWork(10000000));

taskB.Wait(100); //Wait for 100 ms. 

if (taskB.IsCompleted)

Console.WriteLine("taskB has completed.");

else 

Console.WriteLine("Timed out before taskB completed.");

// Wait for all tasks to complete. 

Task[] tasks = new Task[10];

Page 26: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 26/41

 

  for (int i = 0; i < 10; i++)

{

tasks[i] = Task.Factory.StartNew(() => DoSomeWork(10000000));

}

Task.WaitAll(tasks);

// Wait for first task to complete. 

Task<double>[] tasks2 = new Task<double>[3];

// Try three different approaches to the problem. Take the first one. 

tasks2[0] = Task<double>.Factory.StartNew(() => TrySolution1());

tasks2[1] = Task<double>.Factory.StartNew(() => TrySolution2());

tasks2[2] = Task<double>.Factory.StartNew(() => TrySolution3());

int index = Task.WaitAny(tasks2);

double d = tasks2[index].Result;

Console.WriteLine("task[{0}] completed first with result of {1}.", index, d);

Console.ReadKey();

}

private static void DoSomeWork(int val)

{

// Pretend to do something. 

Thread.SpinWait(val);

}

private static double TrySolution1()

{

int i = rand.Next(1000000);

// Simulate work by spinning  

Thread.SpinWait(i);

return DateTime.Now.Millisecond;

}

private static double TrySolution2()

{

int i = rand.Next(1000000);

// Simulate work by spinning  Thread.SpinWait(i);

return DateTime.Now.Millisecond;

}

private static double TrySolution3()

{

int i = rand.Next(1000000);

// Simulate work by spinning  

Thread.SpinWait(i);

Thread.SpinWait(1000000);

return DateTime.Now.Millisecond;

}

}

}

Por razones de simplicidad, estos ejemplos no muestran el código de control de excepciones ni el código de cancelación. En la mayoría de los casos, debe inclu

un método Wait en un bloque try-catch, porque la espera es el mecanismo por el que el código de programa controla las excepciones que se inician e

cualquiera de las tareas. Si la tarea se puede cancelar, debe comprobar las propiedades  IsCanceled o IsCancellationRequested() antes de intentar utilizar la taro su propiedad Result().

Cancelar una tarea y sus elementos secundarios

El subproceso que realiza la llamada no finaliza la tarea forzosamente, sino que solo señala que se solicita la cancelación. Si la tarea ya se está ejecutando, es e

delegado de usuario el que debe observar la solicitud y responder según corresponda. Si la cancelación se solicita antes de ejecutarse la tarea, el delegado d

usuario nunca se ejecuta y el objeto de tarea pasa al estado Cancelado.

En este ejemplo se muestra cómo finalizar un objeto Task y sus elementos secundarios en respuesta a una solicitud de cancelación. También se muestra qu

cuando un delegado de usuario finaliza con una excepción  OperationCanceledException , el subproceso que realiza la llamada puede usar opcionalmente

método Wait o el método WaitAll para esperar a que las tareas finalicen. En este caso, el delegado debe usar un bloque try-catch para controlar las excepcion

en el subproceso que realiza la llamada.

using System;

using System.Threading;

using System.Threading.Tasks;

namespace Paralell_Practices 

{

internal class CancellationWithOCE 

{

private static void Main(string[] args)

{

Console.WriteLine("Press any key to start. Press 'c' to cancel." );

Console.ReadKey();

var tokenSource = new CancellationTokenSource();

var token = tokenSource.Token;

Page 27: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 27/41

 

 

// Store references to the tasks so that we can wait on them and  

// observe their status after cancellation. 

Task[] tasks = new Task[10];

// Request cancellation of a single task when the token source is canceled.  

// Pass the token to the us er delegate, and also to the task so it can

// handle the exception correctly. 

tasks[0] = Task.Factory.StartNew(() => DoSomeWork(1, token), token);

// Request cancellation of a task and its children. Note the token is passed  

// to (1) the user delegate and (2) as the second argument to StartNew, so

// that the task instance can correctly handle the OperationCanceledException.  

tasks[1] = Task.Factory.StartNew(() =>

{

// Create some cancelable child tasks. 

for (int i = 2; i < 10; i++)

{

// For each child task, pass the same token  

// to each user delegate and to S tartNew. 

tasks[i] = Task.Factory.StartNew(iteration =>

DoSomeWork((int) iteration, token), i, token);

}

// Passing the same token again to do work on the parent task.

// All will be signaled by the call to tokenSource.Cancel below.  

DoSomeWork(2, token);

}, token);

// Give the tasks a second to start.  

Thread.Sleep(1000);

// Request cancellation from the UI thread. 

if (Console.ReadKey().KeyChar == 'c')

{

tokenSource.Cancel();Console.WriteLine("\nTask cancellation requested.");

// Optional: Observe the change in the Status property on the task.  

// It is not necessary to wait on tasks that have canceled. However,  

// if you do wait, you must enclose the call in a try-catch block to  

// catch the OperationCanceledExceptions that are thrown. If you do

// not wait, no OCE is thrown if the token that was passed to the

// StartNew method is the same token that requested the cancellation. 

#region Optional_WaitOnTasksToComplete

try 

{

Task.WaitAll(tasks);

}

catch (AggregateException e)

{

// For demonstration purposes, show the OCE message. 

foreach (var v in e.InnerExceptions)

Console.WriteLine("msg: " + v.Message);

}

// Prove that the tasks are now all in a canceled state.  

for (int i = 0; i < tasks.Length; i++)

Console.WriteLine("task[{0}] status is now {1}", i, tasks[i].Status);

#endregion 

}

// Keep the console window open while the  

// task completes its output.  

Console.ReadLine();

}

private static void DoSomeWork(int taskNum, CancellationToken ct)

{

// Was cancellation already requested? 

if (ct.IsCancellationRequested)

{

Console.WriteLine("We were cancelled before we got started.");

Console.WriteLine("Press Enter to quit.");

ct.ThrowIfCancellationRequested();}

int maxIterations = 1000;

// NOTE!!! A benign "OperationCanceledException was unhandled  

// by user code" error might be raised here. Press F5 to continue. Or, 

// to avoid the error, uncheck the "Enable Just My Code" 

// option under Tools > Options > Debugging.  

for (int i = 0; i < maxIterations; i++)

{

// Do a bit of work. Not too much. 

var sw = new SpinWait();

for (int  j = 0; j < 3000; j++) sw.SpinOnce();

Console.WriteLine("...{0} ", taskNum);

if (ct.IsCancellationRequested)

Page 28: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 28/41

 

{

Console.WriteLine("bye from {0}.", taskNum);

Console.WriteLine("\nPress Enter to quit.");

ct.ThrowIfCancellationRequested();

}

}

}

}

}

Controlar excepciones iniciadas por tareas

En este primer ejemplo, se detecta la excepción System.AggregateException  y, a continuación, se examina su propiedad AggregateExceptionInnerExceptions()

para determinar si algunas de las excepciones se pueden controlar mediante el código del programa.  

static string[] GetAllFiles(string str)

{

// Should throw an AccessDenied exception on Vista.  

return System.IO.Directory.GetFiles(str, "*.txt", System.IO.SearchOption.AllDirectories);

}

static void HandleExceptions()

{

// Assume this is a user-entered string. 

string path = @"C:\";

// Use this line to throw UnauthorizedAccessException, which we handle. 

Task<string[]> task1 = Task<string[]>.Factory.StartNew(() => GetAllFiles(path));

// Use this line to throw an exception that is not handled.  

// Task task1 = Task.Factory.StartNew(() => { throw new IndexOutOfRangeException(); } ); 

try 

{

task1.Wait();

}

catch (AggregateException ae)

{

ae.Handle((x) =>

{

if (x is UnauthorizedAccessException) // This we know how to handle. 

{

Console.WriteLine("You do not have permission to access all folders in this path." );

Console.WriteLine("See your network administrator or try another path.");

return true;

}

return false; // Let anything else stop the application. 

});

}

Console.WriteLine("task1 has completed.");

}

En este ejemplo, se detecta la excepción System.AggregateException , pero no se intenta controlar ninguna de sus excepciones internas. En su lugar, se utiliza

método Flatten  para extraer las excepciones internas de todas las instancias anidadas de AggregateException  y volver a iniciar una sola excepció

AggregateException  que contiene directamente todas las excepciones internas no controladas. Al reducir la excepción, resulta más fácil controlarla mediant

código de cliente.

static string[] GetValidExtensions(string path)

{

if (path == @"C:\")

throw new ArgumentException("The system root is not a valid path." );

return new string[10];

}

static void RethrowAllExceptions()

{

Page 29: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 29/41

 

  // Assume this is a user-entered string. 

string path = @"C:\";

Task<string[]>[] tasks = new Task<string[]>[3];

tasks[0] = Task<string[]>.Factory.StartNew(() => GetAllFiles(path));

tasks[1] = Task<string[]>.Factory.StartNew(() => GetValidExtensions(path));

tasks[2] = Task<string[]>.Factory.StartNew(() => new string[10]);

//int index = Task.WaitAny(tasks2); //double d = tasks2[index].Result; 

try 

{

Task.WaitAll(tasks);

}

catch (AggregateException ae)

{

throw ae.Flatten();

}

Console.WriteLine("task1 has completed.");

}

Encadenar varias tareas con continuaciones

En la biblioteca TPL, una tarea cuyo método ContinueWith se invoca se denomina tarea antecedente y la tarea que se define en el método ContinueWith se

denomina continuación. En este ejemplo se muestra cómo usar los métodos  ContinueWith y ContinueWith de las clases Task y Task<TResult> para especificar

una tarea que se inicia después de que finalice su tarea antecedente.

También muestra cómo especificar una continuación que solo se ejecuta si la tarea antecedente se cancela.

En estos ejemplos se muestra cómo continuar desde una tarea única. También puede crear una continuación que se ejecute después de que alguno o todos los

grupos de tareas se completen o se cancelen. Para obtener más información, vea TaskContinueWhenAll() y TaskContinueWhenAny().

En el método DoSimpleContinuation, se muestra la sintaxis básica de ContinueWith. Observe que la tarea antecedente se proporciona como el parámetro d

entrada a la expresión lambda en el método ContinueWith. Esto le permite evaluar el estado de la tarea antecedente antes de realizar cualquier trabajo en

continuación. Utilice esta sobrecarga simple de ContinueWith cuando no tenga que pasar un estado de una tarea a otra.

En el método DoSimpleContinuationWithState, se muestra cómo utilizar ContinueWith para pasar el resultado de la tarea antecedente a la tarea de

continuación.

using System;

using System.IO;

using System.Linq;

using System.Threading;

using System.Threading.Tasks;

namespace Paralell_Practices 

{

internal class ContinueWith 

{

private static void Main()

{

SimpleContinuation();

Console.WriteLine("Press any key to exit");

Console.ReadKey();

}

private static void SimpleContinuation()

{

string path = @"C:\users\public\TPLTestFolder\";

try 

{

var firstTask = new Task(() => CopyDataIntoTempFolder(path));

var secondTask = firstTask.ContinueWith((t) => CreateSummaryFile(path));

firstTask.Start();

}

catch (AggregateException e)

{

Console.WriteLine(e.Message);

Page 30: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 30/41

 

}

}

// A toy function to simulate a workload 

private static void CopyDataIntoTempFolder(string path)

{

System.IO.Directory.CreateDirectory(path);

Random rand = new Random();

for (int x = 0; x < 50; x++)

{

byte[] bytes = new byte[1000];

rand.NextBytes(bytes);

string filename = Path.GetRandomFileName();

string filepath = Path.Combine(path, filename);

System.IO.File.WriteAllBytes(filepath, bytes);

}

}

private static void CreateSummaryFile(string path)

{

string[] files = System.IO.Directory.GetFiles(path);

Parallel.ForEach(files, (file) =>

{

Thread.SpinWait(5000);

});

System.IO.File.WriteAllText(Path.Combine(path, "__SummaryFile.txt"), "did my work");

Console.WriteLine("Done with task2");

}

private static void SimpleContinuationWithState()

{

int[] nums = {19, 17, 21, 4, 13, 8, 12, 7, 3, 5};

var f0 = new Task<double>(() => nums.Average());

var f1 = f0.ContinueWith(t => GetStandardDeviation(nums, t.Result));

f0.Start();

Console.WriteLine("the standard deviation is {0}", f1.Result);

}

private static double GetStandardDeviation(int[] values, double mean)

{

double d = 0.0;

foreach (var n in values)

{

d += Math.Pow(mean - n, 2);

}

return Math.Sqrt(d/(values.Length - 1));

}

}

}

El parámetro de tipo de Task<TResult> determina el tipo devuelto del delegado. Ese valor devuelto se pasa a la tarea de continuación. Es posible encadenar un

número arbitrario de tareas de esta manera.

Recorrer un árbol binario con tareas paralelas

public class TreeWalk

{

static void Main()

{

Tree<MyClass> tree = new Tree<MyClass>();

// ...populate tree (left as an exercise) 

// Define the Action to perform on each node.  

Action<MyClass> myAction = x => Console.WriteLine("{0} : {1}", x.Name, x.Number);

// Traverse the tree with parallel tasks. DoTree(tree, myAction);

}

public class MyClass

{

public string Name { get; set; }

public int Number { get; set; }

}

public class Tree<T>

{

public Tree<T> Left;

Page 31: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 31/41

 

  public Tree<T> Right;

public T Data;

}

// By using tasks explcitly. 

public static void DoTree<T>(Tree<T> tree, Action<T> action)

{

if (tree == null) return;

var left = Task.Factory.StartNew(() => DoTree(tree.Left, action));var right = Task.Factory.StartNew(() => DoTree(tree.Right, action));

action(tree.Data);

try 

{

Task.WaitAll(left, right);

}

catch (AggregateException )

{

//handle exceptions here 

}

}

// By using Parallel.Invoke 

public static void DoTree2<T>(Tree<T> tree, Action<T> action)

{

if (tree == null) return;

Parallel.Invoke(

() => DoTree2(tree.Left, action),

() => DoTree2(tree.Right, action),

() => action(tree.Data)

);

}

}

Los dos métodos mostrados son equivalentes desde el punto de vista funcional. Cuando se usa el método StartNew() para crear y ejecutar las tareas, estas

devuelven un identificador que se puede usar para esperar en ellas y controlar las excepciones.

TPL con otros modelos asincrónicos

La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) se puede usar con modelos tradicionales de programación

asincrónica de .NET de varias maneras.

TPL y la programación asincrónica tradicional de .NET

.NET Framework proporciona los siguientes dos modelos estándar para realizar las operaciones asincrónicas enlazadas a E/S y enlazadas a cálculos:

  Modelo de programación asincrónica (APM), en el que las operaciones asincrónicas se representan mediante un par de métodos Begin/End como

FileStream.BeginRead y Stream.EndRead. 

  Modelo asincrónico basado en eventos (EAP), en el que las operaciones asincrónicas se representan mediante un par método-evento que se

denomina OperationNameAsync y OperationNameCompleted, por ejemplo, WebClient.DownloadStringAsync  yWebClient.DownloadStringCompleted . (EAP apareció por primera vez en .NET Framework versión 2.0).

La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) se puede usar de varias maneras junto con cualquiera de l

modelos asincrónicos. Puede exponer las operaciones de APM y EAP como tareas a los consumidores de la biblioteca o puede exponer los modelos de APM, pe

usar objetos de tarea para implementarlos internamente. En ambos escenarios, al usar los objetos de tarea, puede simplificar el código y aprovechar la siguient

funcionalidad útil:

  Registre las devoluciones de llamada, en el formulario de continuaciones de la tarea, en cualquier momento después de que se haya iniciado la tarea

Page 32: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 32/41

 

  Coordine varias operaciones que se ejecutan en respuesta a un método Begin_, mediante los métodos ContinueWhenAny, ContinueWhenAll, WaitA

o WaitAny. 

  Encapsule las operaciones asincrónicas enlazadas a E/S y enlazadas a cálculos en el mismo objeto de tarea.

  Supervise el estado del objeto de tarea.

  Calcule las referencias del estado una operación para un objeto de tarea mediante  TaskCompletionSource<TResult> .

Ajustar las operaciones de APM en una tarea 

Las clases System.Threading.Tasks.TaskFactory   y System.Threading.Tasks.TaskFactory<TResult>   proporcionan varias sobrecargas de los métodosFromAsyyFromAsyncque permiten encapsular un par de métodos Begin/End en una instancia de Task o de Task<TResult>. Las diversas sobrecargas hospedan cualqu

par de métodos de Begin/End que tenga entre cero y tres parámetros de entrada.

Para los pares que tienen métodos End que devuelven un valor (Function en Visual Basic), use los métodos de  TaskFactory<TResult>, que crean un obje

Task<TResult>. Para los métodos End que devuelven un valor void (Sub en Visual Basic), use los métodos de TaskFactory, que crean un objeto Task. 

En los pocos casos en los que el método Begin tiene más de tres parámetros o contiene parámetros out o ref, se proporcionan las sobrecargas FromAsync 

adicionales que encapsulan sólo el método End.

En el ejemplo de código siguiente se muestra la signatura para la sobrecarga FromAsync que coincide con los métodos FileStream.BeginRead  y

FileStream.EndRead. Esta sobrecarga toma los tres parámetros de entrada siguientes.

public Task<TResult> FromAsync<TArg1, TArg2, TArg3>(

Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, //BeginRead Func<IAsyncResult, TResult> endMethod, //EndRead 

TArg1 arg1, // the byte[] buffer 

TArg2 arg2, // the offset in arg1 at which to start writing data  

TArg3 arg3, // the maximum number of bytes to read 

object state // optional state information 

)

El primer parámetro es un delegado Func<T1, T2, T3, T4, T5, TResult> que coincide con la signatura del método FileStream.BeginRead. El segundo parámetro

un delegado Func<T, TResult> que toma una interfaz IAsyncResult y devuelve TResult. Dado que EndRead devuelve un entero, el compilador deduce el tipo

TResult como Int32 y el tipo de la tarea como Task<Int32>. Los últimos cuatro parámetros son idénticos a los del método  FileStream.BeginRead: 

  Búfer donde se van a almacenar los datos de archivo.

  Desplazamiento en el búfer donde deben comenzar a escribirse los datos.

  Cantidad máxima de datos que se van a leer del archivo.

  Un objeto opcional que almacena los datos de estado definidos por el usuario que se van a pasar a la devolución de llamada.

Usar ContinueWith para la funcionalidad de devolución de llamada

Si necesita obtener acceso a los datos del archivo, en contraposición a solo el número de bytes, el método  FromAsync  no es suficiente. En su ligar, u

Task<String>, cuya propiedad Result contiene los datos de archivo. Puede hacer si agrega una continuación a la tarea original. La continuación realiza el traba

que normalmente realizaría el delegado AsyncCallback. Se invoca cuando se completa el antecedente y se ha rellenado el búfer de datos. (El objeto FileStream 

debería cerrar antes de devolver un valor).

En el siguiente ejemplo se muestra cómo devolver un objeto Task<String> que encapsula el par BeginRead/EndRead de la clase FileStream. 

const int MAX_FILE_SIZE = 14000000;

public static Task<string> GetFileStringAsync(string path){

FileInfo fi = new FileInfo(path);

byte[] data = null;

data = new byte[fi.Length];

FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, true);

//Task<int> returns the number of bytes read 

Task<int> task = Task<int>.Factory.FromAsync(

fs.BeginRead, fs.EndRead, data, 0, data.Length, null);

Page 33: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 33/41

 

  // It is possible to do other work here while waiting  

// for the antecedent task to complete. 

// ... 

// Add the continuation, which returns a Task<string>.

return task.ContinueWith((antecedent) =>

{

fs.Close();

// Result = "number of bytes read" (if we need it.)  

if (antecedent.Result < 100){

return "Data is too small to bother with.";

}

else 

{

// If we did not receive the entire file, the end of the  

// data buffer will contain garbage. 

if (antecedent.Result < data.Length)

Array.Resize(ref data, antecedent.Result);

// Will be returned in the Result property of the Task<string> 

// at some future point after the asynchronous file I/O operation completes. 

return new UTF8Encoding().GetString(data);

}

});

}

A continuación, se puede llamar al método de la forma siguiente.

Task<string> t = GetFileStringAsync(path);

// Do some other work: 

// ... 

try 

{

Console.WriteLine(t.Result.Substring(0, 500));

}

catch (AggregateException ae)

{ Console.WriteLine(ae.InnerException.Message);

}

Proporcionar los datos de estado personalizados

En las operaciones IAsyncResult  típicas, si el delegado  AsyncCallback  requiere algún dato de estado personalizado, tiene que pasarlo a través del últim

parámetro Begin para que los datos se puedan empaquetar en el objeto IAsyncResult  que se pasará finalmente al método de devolución de llamad

Normalmente no se requiere esto cuando se usan los métodos FromAsync. Si los datos personalizados son conocidos para la continuación, se pueden captur

directamente en el delegado de continuación. El siguiente ejemplo se parece el ejemplo anterior, pero en lugar de examinar la propiedad Result d

antecedente, la continuación examina los datos de estado personalizados que son directamente accesibles al delegado de usuario de la continuación.

public Task<string> GetFileStringAsync2(string path)

{

FileInfo fi = new FileInfo(path);

byte[] data = new byte[fi.Length];

MyCustomState state = GetCustomState();

FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, true);

// We still pass null for the last parameter because  

// the state variable is visible to the continuation delegate. 

Task<int> task = Task<int>.Factory.FromAsync(

fs.BeginRead, fs.EndRead, data, 0, data.Length, null);

return task.ContinueWith((antecedent) =>

{

Page 34: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 34/41

 

  // It is safe to close the filestream now.  

fs.Close();

// Capture custom state data directly in the user delegate. 

// No need to pass it through the FromAsync method. 

if (state.StateData.Contains("New York, New York"))

{

return "Start spreading the news!";

}

else 

{// If we did not receive the entire file, the end of the  

// data buffer will contain garbage. 

if (antecedent.Result < data.Length)

Array.Resize(ref  data, antecedent.Result);

// Will be returned in the Result property of the Task<string> 

// at some future point after the asynchronous file I/O operation completes. 

return new UTF8Encoding().GetString(data);

}

});

}

Sincronizar varias tareas FromAsync

Los métodos estáticos ContinueWhenAny y ContinueWhenAll proporcionan flexibilidad adicional cuando se usan junto con los métodos FromAsync. El siguient

ejemplo muestra cómo iniciar varias operaciones asincrónicas de E/S y, a continuación, espera a que todos ellas se completen antes de ejecutar la continuación.

public Task<string> GetMultiFileData(string[] filesToRead)

{

FileStream fs;

Task<string>[] tasks = new Task<string>[filesToRead.Length];

byte[] fileData = null;

for (int i = 0; i < filesToRead.Length; i++)

{

fileData = new byte[0x1000];

fs = new FileStream(filesToRead[i], FileMode.Open, FileAccess.Read, FileShare.Read, fileData.Length, true);

// By adding the continuation here, the

// Result of each task will be a string.  

tasks[i] = Task<int>.Factory.FromAsync(

fs.BeginRead, fs.EndRead, fileData, 0, fileData.Length, null)

.ContinueWith((antecedent) =>

{

fs.Close();

// If we did not receive the entire file, the end of the  

// data buffer will contain garbage. 

if (antecedent.Result < fileData.Length)

Array.Resize(ref  fileData, antecedent.Result);

// Will be returned in the Result property of the Task<string> 

// at some future point after the asynchronous file I/O operation completes. 

return new UTF8Encoding().GetString(fileData);

});

}

// Wait for all tasks to complete.

return Task<string>.Factory.ContinueWhenAll(tasks, (data) =>

{

// Propagate all exceptions and mark all faulted tasks as observed. 

Task.WaitAll(data);

// Combine the results from all tasks. 

Page 35: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 35/41

 

  StringBuilder sb = new StringBuilder();

foreach (var t in data)

{

sb.Append(t.Result);

}

// Final result to be returned eventually on the calling thread. 

return sb.ToString();

});

}

Tareas FromAsync solo para el método End

En los pocos casos en los que el método Begin requiere más de tres parámetros de entrada o tiene parámetros out o ref, puede usar las sobrecargas FromAsyn

por ejemplo,  TaskFactory<TResult>.FromAsync(IAsyncResult, Func<IAsyncResult, TResult>), que representa sólo el método End. Estos métodos también

pueden usar en cualquier escenario en el que se pasa IAsyncResult y desea encapsularlo en una tarea.

static Task<String> ReturnTaskFromAsyncResult()

{

IAsyncResult ar = DoSomethingAsynchronously();

Task<String> t = Task<string>.Factory.FromAsync(ar, _ =>

{

return (string)ar.AsyncState;

});

return t;}

Iniciar y cancelar las tareas FromAsync

La tarea devuelta por un método FromAsync tiene un estado de WaitingForActivation y la iniciará el sistema en algún momento una vez creada la tarea. Si

intenta llamar a Start en este tipo de tarea, se producirá una excepción.

No puede cancelar una tarea FromAsync, porque las API subyacentes de .NET Framework admiten actualmente la cancelación en curso del la E/S de archivo

red. Puede agregar la funcionalidad de cancelación a un método que encapsula una llamada FromAsync, pero sólo puede responder a la cancelación antes

que se llame a FromAsync o después de completar (por ejemplo, en una tarea de continuación).

Algunas clases que admiten EAP, por ejemplo, WebClient, admiten la cancelación y esa funcionalidad de cancelación nativa se puede integrar mediante los

tokens de cancelación.

Exponer las operaciones de EAP complejas como tareas  

La TPL no proporciona ningún método diseñado específicamente para encapsular una operación asincrónica basada en eventos del mismo modo que la famil

de métodos FromAsync ajusta el modelo IAsyncResult. Sin embargo, TPL proporciona la clase System.Threading.Tasks.TaskCompletionSource<TResult> , que

puede usar para representar cualquier conjunto arbitrario de operaciones como Task<TResult>. Las operaciones pueden ser sincrónicas o asincrónicas y pued

ser enlazadas a E/S o enlazadas a cálculo, o ambos.

En el siguiente ejemplo se muestra cómo usar TaskCompletionSource<TResult>  para exponer un conjunto de operaciones WebClient asincrónicas al código

cliente como un objeto Task básico. El método permite escribir una matriz de direcciones URL de web y un término o nombre que se va a buscar y,

continuación, devuelve el número de veces que aparece el término de búsqueda en cada sitio.

Task<string[]> GetWordCountsSimplified(string[] urls, string name, CancellationToken token)

{

TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();

WebClient[] webClients = new WebClient[urls.Length];object m_lock = new object();

int count = 0;

List<string> results = new List<string>();

// If the user cancels the Cancel lationToken, then we can use the  

// WebClient's ability to cancel its own async operations.  

token.Register(() =>

{

foreach (var wc in webClients)

{

if (wc != null)

wc.CancelAsync();

}

});

Page 36: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 36/41

 

 

for (int i = 0; i < urls.Length; i++)

{

webClients[i] = new WebClient();

#region callback

// Specify the callback for the DownloadStringCompleted 

// event that will be raised by this WebClient instance. 

webClients[i].DownloadStringCompleted += (obj, args) =>

{

// Argument validation and exception handling omitted for brevity.  

// Split the string into an array of words,  

// then count the number of elements that match 

// the search term. 

string[] words = args.Result.Split(' ');

string NAME = name.ToUpper();

int nameCount = (from word in words.AsParallel()

where word.ToUpper().Contains(NAME)

select word).Count();

// Associate the results with the url, and add new string to the array that

// the underlying Task object will return in its Result property. 

results.Add(String.Format("{0} has {1} instances of {2}", args.UserState,

nameCount, name));

// If this is the last async operation to complete,  

// then set the Result property on the underlying Task. 

lock (m_lock)

{

count++;

if (count == urls.Length)

{

tcs.TrySetResult(results.ToArray());

}}

};

#endregion 

// Call DownloadStringAsync for each URL. 

Uri address = null;

address = new Uri(urls[i]);

webClients[i].DownloadStringAsync(address, address);

} // end for 

// Return the underlying Task. The client code 

// waits on the Result property, and han dles exceptions 

// in the try-catch block there. 

return tcs.Task;

}

Recuerde que TaskCompletionSource iniciará cualquier tarea creada por TaskCompletionSource<TResult>  y, por consiguiente, el código de usuario no debería

llamar al método Start en esa tarea.

Implementar el modelo de APM usando las tareas 

En algunos escenarios, puede ser deseable exponer directamente el modelo IAsyncResult mediante pares de métodos Begin/End en una API. Por ejemplo, quiz

desee mantener la coherencia con las API existentes o puede haber automatizado herramientas que requieren este modelo. En tales casos, puede usar las tarea

para simplificar la forma en que se implementa internamente el modelo de APM.

En el siguiente ejemplo se muestra cómo usar las tareas para implementar un par de métodos Begin/End de APM para un método enlazado a cálculo de

ejecución prolongada.

using System;

using System.Threading;

using System.Threading.Tasks;

namespace Paralell_Practices 

{

internal class Calculator 

{

public IAsyncResult BeginCalculate(int decimalPlaces, AsyncCallback ac, object state)

{

Console.WriteLine("Calling BeginCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId);

Task<string> f = Task<string>.Factory.StartNew( _ => Compute(decimalPlaces), state);

if (ac != null) f .ContinueWith((res) => ac(f ));

return f ;

}

public string Compute(int numPlaces)

{

Console.WriteLine("Calling compute on thread {0}", Thread.CurrentThread.ManagedThreadId);

// Simulating some heavy work. 

Page 37: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 37/41

 

  Thread.SpinWait(500000000);

// Actual implemenation left as exercise for the reader.  

// Several examples are available on the Web. 

return "3.14159265358979323846264338327950288" ;

}

public string EndCalculate(IAsyncResult ar)

{

Console.WriteLine("Calling EndCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId);

return ((Task<string>) ar).Result;

}

}

public class CalculatorClient 

{

private static int decimalPlaces = 12;

public static void Main()

{

Calculator calc = new Calculator();

int places = 35;

AsyncCallback callBack = new AsyncCallback(PrintResult);

IAsyncResult ar = calc.BeginCalculate(places, callBack, calc);

// Do some work on this thread while the calulator is busy.  

Console.WriteLine("Working...");

Thread.SpinWait(500000);

Console.ReadLine();

}

public static void PrintResult(IAsyncResult result)

{

Calculator c = (Calculator) result.AsyncState;string piString = c.EndCalculate(result);

Console.WriteLine("Calling PrintResult on thread {0}; result = {1}",

Thread.CurrentThread.ManagedThreadId, piString);

}

}

}

Encapsular modelos de EAP en una tarea

En el ejemplo siguiente, se muestra cómo exponer una secuencia arbitraria de operaciones asincrónicas de host del Protocolo de autenticación extensible (EAP

como una sola tarea mediante TaskCompletionSource<TResult> . El ejemplo también muestra cómo usar CancellationToken para invocar los métodos de

cancelación integrados en los objetos WebClient. 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Threading;

using System.Threading.Tasks;

namespace Paralell_Practices 

{

internal class WebDataDownloader 

{

private static void Main()

{

WebDataDownloader downloader = new WebDataDownloader();

string[] addresses = {

"http://www.msnbc.com", "http://www.yahoo.com",

"http://www.nytimes.com", "http://www.washingtonpost.com",

"http://www.latimes.com", "http://www.newsday.com" 

};

CancellationTokenSource cts = new CancellationTokenSource();

// Create a UI thread from which to cancel the entire operation 

Task.Factory.StartNew(() =>

{

Console.WriteLine("Press c to cancel");

if (Console.ReadKey().KeyChar == 'c')

cts.Cancel();

});

// Using a neutral search term that is sure to get some hits.  

Task<string[]> webTask = downloader.GetWordCounts(addresses, "the", cts.Token);

// Do some other work here unless the method has already completed.  

if (!webTask.IsCompleted)

{

// Simulate some work. 

Thread.SpinWait(5000000);

}

Page 38: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 38/41

 

 

string[] results = null;

try 

{

results = webTask.Result;

}

catch (AggregateException e)

{

foreach (var ex in e.InnerExceptions)

{

OperationCanceledException oce = ex as OperationCanceledException;

if (oce != null)

{

if (oce.CancellationToken == cts.Token)

{

Console.WriteLine("Operation canceled by user.");

}

}

else 

Console.WriteLine(ex.Message);

}

}

if (results != null)

{

foreach (var item in results)

Console.WriteLine(item);

}

Console.ReadKey();

}

private Task<string[]> GetWordCounts(string[] urls, string name, CancellationToken token)

{

TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();

WebClient[] webClients = new WebClient[urls.Length];

// If the user cancels the Cancel lationToken, then we can use the  

// WebClient's ability to cancel its own async operations.  

token.Register(() =>

{

foreach (var wc in webClients)

{

if (wc != null)

wc.CancelAsync();

}

});

object m_lock = new object();

int count = 0;

List<string> results = new List<string>();

for (int i = 0; i < urls.Length; i++)

{

webClients[i] = new WebClient();

#region callback

// Specify the callback for the DownloadStringCompleted 

// event that will be raised by this WebClient instance. 

webClients[i].DownloadStringCompleted += (obj, args) =>

{

if (args.Cancelled == true)

{

tcs.TrySetCanceled();

return;

}

else if (args.Error != null)

{

// Pass through to the underlying Task  

// any exceptions thrown by the WebClient 

// during the asynchronous operation. 

tcs.TrySetException(args.Error);

return;

}

else 

{

// Split the string into an array of words,  

// then count the number of elements that match // the search term. 

string[] words = null;

words = args.Result.Split(' ');

string NAME = name.ToUpper();

int nameCount = (from word in words.AsParallel()

where word.ToUpper().Contains(NAME)

select word)

.Count();

// Associate the results with the url, and add new string to the array that

// the underlying Task object will return in its Result property. 

results.Add(String.Format("{0} has {1} instances of {2}", args.UserState,

nameCount, name));

}

Page 39: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 39/41

 

 

// If this is the last async operation to complete,  

// then set the Result property on the underlying Task. 

lock (m_lock)

{

count++;

if (count == urls.Length)

{

tcs.TrySetResult(results.ToArray());

}

}

};

#endregion 

// Call DownloadStringAsync for each URL. 

Uri address = null;

try 

{

address = new Uri(urls[i]);

// Pass the address, and also use it for the userToken

// to identify the page when the delegate is invoked. 

webClients[i].DownloadStringAsync(address, address);

}

catch (UriFormatException ex)

{

// Abandon the entire operation if one url is malformed. 

// Other actions are possible here. 

tcs.TrySetException(ex);

return tcs.Task;

}

}

// Return the underlying Task. The client code // waits on the Result property, and han dles exceptions 

// in the try-catch block there. 

return tcs.Task;

}

}

Problemas potenciales en el paralelismo de datos y tareas

En muchos casos,  Parallel.For  y Parallel.ForEach  pueden proporcionar mejoras de rendimiento significativas en comparación con los bucles secuencial

normales. Sin embargo, el trabajo de paralelizar el bucle aporta una complejidad que puede llevar a problemas que, en código secuencial, no son tan comunes

que no se producen en absoluto. En este tema se indican algunas prácticas que se deben evitar al escribir bucles paralelos.

No se debe suponer que la ejecución en paralelo es siempre más rápida 

En algunos casos, un bucle paralelo se podría ejecutar más lentamente que su equivalente secuencial. La regla básica es que no es probable que los bucles en

paralelo que tienen pocas iteraciones y delegados de usuario rápidos aumenten gran cosa la velocidad. Sin embargo, como son muchos los factores implicados

en el rendimiento, recomendamos siempre medir los resultados reales.

Evitar la escritura en ubicaciones de memoria compartidas 

En código secuencial, no es raro leer o escribir en variables estáticas o en campos de clase. Sin embargo, cuando varios subprocesos tienen acceso a est

variables de forma simultánea, hay grandes posibilidades de que se produzcan condiciones de carrera. Aunque se pueden usar bloqueos para sincronizar

acceso a la variable, el costo de la sincronización puede afectar negativamente al rendimiento. Por tanto, se recomienda evitar, o al menos limitar, el acceso a

estado compartido en un bucle en paralelo en la medida de lo posible. La manera mejor de hacerlo es mediante las sobrecargas de Parallel.For y Parallel.ForEa

que utilizan una variable System.Threading.ThreadLocal<T>  para almacenar el estado local del subproceso durante la ejecución del bucle. Para obtener m

información, vea Cómo: Escribir un bucle Parallel.For que tenga variables locales de subproceso y Cómo: Escribir un bucle Parallel.ForEach que tenga variabl

locales de subproceso.

Evitar la paralelización excesiva 

Si usa bucles en paralelo, incurrirá en costos de sobrecarga al crear particiones de la colección de origen y sincronizar los subprocesos de trabajo. El número

procesadores del equipo reduce también las ventajas de la paralelización. Si se ejecutan varios subprocesos enlazados a cálculos en un único procesador, no

gana en velocidad. Por tanto, debe tener cuidado para no paralelizar en exceso un bucle.

El escenario más común en el que se puede producir un exceso de paralelización son los bucles anidados. En la mayoría de los casos, es mejor paralelizar

únicamente el bucle exterior, a menos que se cumplan una o más de las siguientes condiciones:

Page 40: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 40/41

 

  Se sabe que el bucle interno es muy largo.

  Se realiza un cálculo caro en cada pedido. (La operación que se muestra en el ejemplo no es cara.)

  Se sabe que el sistema de destino tiene suficientes procesadores como para controlar el número de subprocesos que se producirán al paralelizar la

consulta de cust.Orders.

En todos los casos, la mejor manera de determinar la forma óptima de la consulta es mediante la prueba y la medición.

Evitar llamadas a métodos que no son seguros para subprocesos 

La escritura en métodos de instancia que no son seguros para subprocesos de un bucle en paralelo puede producir daños en los datos, que pueden pasar o no

inadvertidos para el programa. También puede dar lugar a excepciones. En el siguiente ejemplo, varios subprocesos estarían intentando llamar simultáneament

al método FileStream.WriteByte , lo que no se admite en la clase.

FileStream fs = File.OpenWrite(path);

byte[] bytes = new Byte[10000000];

// ... 

Parallel.For(0, bytes.Length, (i) => fs.WriteByte(bytes[i]));

Limitar las llamadas a métodos seguros para subprocesos 

La mayoría de los métodos estáticos de .NET Framework son seguros para subprocesos y se les pueden llamar simultáneamente desde varios subprocesos. Sin

embargo, incluso en estos casos, la sincronización que esto supone puede conducir a una ralentización importante en la consulta.

Ser consciente de los problemas de afinidad de los subprocesos 

Algunas tecnologías, como la interoperabilidad COM para componentes STA (apartamento de un único subproceso), Windows Forms y Windows Presentatio

Foundation (WPF), imponen restricciones de afinidad de subprocesos que exigen que el código se ejecute en un subproceso determinado. Por ejemplo, tanto e

Windows Forms como en WPF, solo se puede tener acceso a un control en el subproceso donde se creó. Por ejemplo, esto significa que no puede actualizar u

control de lista desde un bucle paralelo a menos que configure el programador del subproceso para que programe trabajo solo en el subproceso de la interfaz d

usuario. Para obtener más información, vea Cómo: Programar el trabajo en un contexto de sincronización especificado.

Tener precaución cuando se espera en delegados a los que llama Parallel.Invoke  

En algunas circunstancias, Task Parallel Library incluirá una tarea, lo que significa que se ejecuta en la tarea del subproceso que se está ejecutando actualment

(Para obtener más información, vea Programadores de tareas). Esta optimización de rendimiento puede acabar en interbloqueo en algunos casos. Por ejemp

dos tareas podrían ejecutar el mismo código de delegado, que señala cuándo se genera un evento, y después esperar a que la otra tarea señale. Si la segun

tarea está alineada en el mismo subproceso que la primera y la primero entra en un estado de espera, la segunda tarea nunca podrá señalar su evento. Paevitar que suceda, puede especificar un tiempo de espera en la operación de espera o utilizar constructores de subproceso explícitos para ayudar a asegurars

de que una tarea no puede bloquear la otra.

No se debe suponer que las iteraciones de Foreach, For y ForAll siempre se ejecutan en paralelo  

Es importante tener presente que las iteraciones individuales de un bucle  For, ForEach o  ForAll<TSource>  tal vez no tengan que ejecutarse en paralelo. P

consiguiente, se debe evitar escribir código cuya exactitud dependa de la ejecución en paralelo de las iteraciones o de la ejecución de las iteraciones en algú

orden concreto. Por ejemplo, es probable que este código lleve a un interbloqueo:

ManualResetEventSlim mre = new ManualResetEventSlim();

Enumerable.Range(0, Environment.ProcessorCount * 100)

.AsParallel()

.ForAll((j) =>

{

if (j == Environment.ProcessorCount)

{

Console.WriteLine("Set on {0} with value of {1}",

Thread.CurrentThread.ManagedThreadId, j);

mre.Set();

}

else 

{

Console.WriteLine("Waiting on {0} with value of {1}",

Thread.CurrentThread.ManagedThreadId, j);

mre.Wait();

Page 41: Programación paralela en .NET Framework

5/10/2018 Programación paralela en .NET Framework - slidepdf.com

http://slidepdf.com/reader/full/programacion-paralela-en-net-framework 41/41

 

}

}); //deadlocks 

En este ejemplo, una iteración establece un evento y el resto de las iteraciones esperan el evento. Ninguna de las iteraciones que esperan puede completars

hasta que se haya completado la iteración del valor de evento. Sin embargo, es posible que las iteraciones que esperan bloqueen todos los subprocesos que

utilizan para ejecutar el bucle paralelo, antes de que la iteración del valor de evento haya tenido oportunidad de ejecutarse. Esto produce un interbloqueo:

iteración del valor de evento nunca se ejecutará y las iteraciones que esperan nunca se activarán.

En concreto, una iteración de un bucle paralelo no debe esperar nunca otra iteración del bucle para progresar. Si el bucle paralelo decide programar lasiteraciones secuencialmente pero en el orden contrario, se producirá un interbloqueo.

Evitar la ejecución de bucles en paralelo en el subproceso de la interfaz de usuario  

Es importante mantener la interfaz de usuario de la aplicación (UI) capaz de reaccionar. Si una operación contiene bastante trabajo para garantizar

paralelización, no se debería ejecutar en el subproceso de la interfaz de usuario. Conviene descargarla para que se ejecute en un subproceso en segundo plan

Por ejemplo, si desea utilizar un bucle paralelo para calcular datos que después se presentarán en un control de IU, considere ejecutar el bucle dentro de un

instancia de la tarea, en lugar de directamente en un controlador de eventos de IU. Solo si el cálculo básico se ha completado se deberían calcular las referenci

de la actualización de nuevo en el subproceso de la interfaz de usuario.

Si ejecuta bucles paralelos en el subproceso de la interfaz de usuario, tenga el cuidado de evitar la actualización de los controles de la interfaz de usuario desde

interior del bucle. Si se intenta actualizar los controles de la interfaz de usuario desde dentro de un bucle paralelo que se está ejecutando en el subproceso de

interfaz de usuario, se puede llegar a dañar el estado, a producir excepciones, actualizaciones atrasadas e incluso interbloqueos, dependiendo de cómo

invoque la actualización de la interfaz de usuario. En el siguiente ejemplo, el bucle paralelo bloquea el subproceso de la interfaz de usuario en el que se est

ejecutando hasta que todas las iteraciones se completan. Sin embargo, si se está ejecutando una iteración del bucle en un subproceso en segundo plano (com

puede hacer For), la llamada a Invoke produce que se envíe un mensaje al subproceso de la interfaz de usuario, que se bloquea mientras espera a que e

mensaje se procese. Puesto que se bloquea el subproceso de la interfaz de usuario cuando se ejecuta For, el mensaje no se procesa nunca y el subproceso de

interfaz de usuario se interbloquea.

private void button1_Click(object sender, EventArgs e)

{

Parallel.For(0, N, i =>

{

// do work for i 

button1.Invoke((Action)delegate { DisplayProgress(i); });

});

}

En el siguiente ejemplo se muestra cómo evitar el interbloqueo mediante la ejecución del bucle dentro de una instancia de la tarea. El bucle no bloquea el

subproceso de la interfaz de usuario y se puede procesar el mensaje.

private void button1_Click(object sender, EventArgs e)

{

Task.Factory.StartNew(() =>

Parallel.For(0, N, i =>

{

// do work for i 

button1.Invoke((Action)delegate { DisplayProgress(i); });

})

);

}