Sie sind auf Seite 1von 107

Programacin Paralela en .

NET Framework
MCT: Luis Dueas Pag 1 de 107
Programacin Paralela en .NET Framework
Muchos equipos y estaciones de trabajo tienen dos o cuatro ncleos (es decir, CPU) que permiten
ejecutar varios subprocesos simultneamente. Se espera que los equipos en un futuro cercano
tengan significativamente ms ncleos. Para aprovecharse del hardware de hoy y del maana, puede
paralelizar el cdigo para distribuir el trabajo entre varios procesadores. En el pasado, la
paralelizacin requera manipulacin de bajo nivel de los subprocesos y bloqueos. Visual Studio 2010
y .NET Framework 4 mejoran la compatibilidad para la programacin paralela proporcionando un
nuevo runtime, nuevos tipos de biblioteca de clases y nuevas herramientas de diagnstico. Estas
caractersticas simplifican el desarrollo en paralelo, de modo que pueda escribir cdigo paralelo
eficaz, especfico y escalable de forma natural sin tener que trabajar directamente con subprocesos ni
el bloque de subprocesos. La siguiente ilustracin proporciona una informacin general de alto nivel
de la arquitectura de programacin paralela en .NET Framework 4.


1. Task Parallel Library
La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) es un
conjunto de API y tipos pblicos de los espacios de nombres System.Threading.Tasks y
System.Threading de .NET Framework versin 4. El propsito de TPL es hacer los desarrolladores de
software ms productivos simplificando el proceso de agregar paralelismo y simultaneidad a las
aplicaciones. TPL escala dinmicamente el grado de simultaneidad para utilizar todos los
procesadores que estn disponibles eficazmente. Adems, la TPL se encarga de la divisin del
trabajo, la programacin de los subprocesos en ThreadPool, la compatibilidad con la cancelacin, la
administracin de los estados y otros detalles de bajo nivel. Al utilizar la TPL, el usuario puede
optimizar el rendimiento del cdigo mientras se centra en el trabajo para el que el programa est
diseado.
A partir de .NET Framework 4, la TPL es el modo preferido de escribir cdigo paralelo y multiproceso.
Sin embargo, no todo el cdigo se presta para la paralelizacin; por ejemplo, si un bucle realiza solo
una cantidad reducida de trabajo en cada iteracin o no se ejecuta para un gran nmero de
iteraciones, la sobrecarga de la paralelizacin puede dar lugar a una ejecucin ms lenta del cdigo.
Adems, al igual que cualquier cdigo multiproceso, la paralelizacin hace que la ejecucin del
programa sea ms compleja. Aunque la TPL simplifica los escenarios de multithreading,
recomendamos tener conocimientos bsicos sobre conceptos de subprocesamiento, por ejemplo,
bloqueos, interbloqueos y condiciones de carrera, para usar la TPL eficazmente.
1.1. Paralelismo de datos (Task Parallel Library)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 2 de 107
El paralelismo de datos hace referencia a los escenarios en los que la misma operacin se realiza
simultneamente (es decir, en paralelo) en elementos de una coleccin o matriz de origen. Varias
sobrecargas de los mtodos 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 coleccin de origen para que varios subprocesos puedan funcionar
simultneamente en segmentos diferentes. TPL admite el paralelismo de datos a travs de la clase
System.Threading.Tasks.Parallel. Esta clase proporciona las implementaciones paralelas basadas en
mtodo de los bucles for y foreach (For y For Each en Visual Basic). Se escribe la lgica del bucle para
un bucle Parallel.For o Parallel.ForEach de forma muy similar a como se escribira un bucle secuencial.
No tiene que crear los subprocesos ni poner en la cola los elementos de trabajo. En bucles bsicos,
no es preciso tomar bloqueos. TPL administra todo el trabajo de bajo nivel. En el siguiente ejemplo
de cdigo se muestra un bucle foreach simple y su equivalente paralelo.
' Sequential version
For Each item In sourceCollection
Process(item)
Next
' Parallel equivalent
Parallel.ForEach(sourceCollection, Sub(item) Process(item))
Cuando un bucle paralelo se ejecuta, la TPL crea particiones del origen de datos para que el bucle
pueda funcionar simultneamente en varias partes. En segundo plano, el programador de tareas crea
particiones de la tarea segn los recursos del sistema y la carga de trabajo. Cuando es posible, el
programador redistribuye el trabajo entre varios subprocesos y procesadores si se desequilibra la
carga de trabajo.
Los mtodos Parallel.ForEach y Parallel.For tienen varias sobrecargas que permiten detener o ejecutar
la ejecucin de bucles, supervisar el estado del bucle en otros subprocesos, mantener el estado de
subprocesos locales, finalizar los objetos de subprocesos locales, controlar el grado de simultaneidad,
etc. Los tipos de aplicacin auxiliar que habilitan esta funcionalidad son ParallelLoopState,
ParallelOptions y ParallelLoopResult, CancellationToken y CancellationTokenSource.

1.1.1. Cmo: Escribir un bucle Parallel.For simple
En este ejemplo se muestra cmo utilizar la sobrecarga ms simple del mtodo Parallel.For para
calcular el producto de dos matrices. Tambin se muestra cmo utilizar la clase
System.Diagnostics.Stopwatch para comparar el rendimiento de un bucle paralelo con un bucle no
paralelo.
Ejemplo
' How to: Write a Simple Parallel.For Loop
Imports System.Threading.Tasks
Module MultiplyMatrices
#Region "Sequential_Loop"
Sub MultiplyMatricesSequential(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As Double(,))
Dim matACols As Integer = matA.GetLength(1)
Dim matBCols As Integer = matB.GetLength(1)
Dim matARows As Integer = matA.GetLength(0)
For i As Integer = 0 To matARows - 1
For j As Integer = 0 To matBCols - 1
For k As Integer = 0 To matACols - 1
result(i, j) += matA(i, k) * matB(k, j)
Next
Next
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 3 de 107
Next
End Sub
#End Region
#Region "Parallel_Loop"
Private Sub MultiplyMatricesParallel(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As Double(,))
Dim matACols As Integer = matA.GetLength(1)
Dim matBCols As Integer = matB.GetLength(1)
Dim matARows As Integer = matA.GetLength(0)
' A basic matrix multiplication. Parallelize the outer loop to partition the source array by rows.
Parallel.For(0, matARows, Sub(i)
For j As Integer = 0 To matBCols - 1
' Use a temporary to improve parallel performance.
Dim temp As Double = 0
For k As Integer = 0 To matACols - 1
temp += matA(i, k) * matB(k, j)
Next
result(i, j) += temp
Next
End Sub)
End Sub
#End Region
#Region "Main"
Sub Main(ByVal args As String())
' 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.
Dim colCount As Integer = 180
Dim rowCount As Integer = 2000
Dim colCount2 As Integer = 270
Dim m1 As Double(,) = InitializeMatrix(rowCount, colCount)
Dim m2 As Double(,) = InitializeMatrix(colCount, colCount2)
Dim result As Double(,) = New Double(rowCount - 1, colCount2 - 1) {}
' First do the sequential version.
Console.WriteLine("Executing sequential loop...")
Dim stopwatch As 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 - 1, colCount2 - 1) {}
' 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)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 4 de 107
' Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
#End Region
#Region "Helper_Methods"
Function InitializeMatrix(ByVal rows As Integer, ByVal cols As Integer) As Double(,)
Dim matrix As Double(,) = New Double(rows - 1, cols - 1) {}
Dim r As New Random()
For i As Integer = 0 To rows - 1
For j As Integer = 0 To cols - 1
matrix(i, j) = r.[Next](100)
Next
Next
Return matrix
End Function
Sub OfferToPrint(ByVal rowCount As Integer, ByVal colCount As Integer, ByVal matrix As Double(,))
Console.WriteLine("Computation complete. Print results? y/n")
Dim c As Char = Console.ReadKey().KeyChar
If c = "y"c OrElse c = "Y"c Then
Console.WindowWidth = 168
Console.WriteLine()
For x As Integer = 0 To rowCount - 1
Console.WriteLine("ROW {0}: ", x)
For y As Integer = 0 To colCount - 1
Console.Write("{0:#.##} ", matrix(x, y))
Next
Console.WriteLine()
Next
End If
End Sub
#End Region
End Module
Puede utilizar la sobrecarga ms bsica del mtodo For si no necesita cancelar ni interrumpir las
iteraciones, ni mantener un estado local de subproceso.
Al paralelizar un cdigo, incluidos los bucles, un objetivo importante consiste en hacer tanto uso de
los procesadores como sea posible, sin excederse hasta el punto de que la sobrecarga del
procesamiento en paralelo anule las ventajas en el rendimiento. En este ejemplo determinado,
solamente se paraleliza el bucle exterior, ya que en el bucle interior no se realiza demasiado trabajo.
La combinacin de una cantidad pequea de trabajo y los efectos no deseados en la memoria cach
puede producir la degradacin del rendimiento en los bucles paralelos anidados. Por consiguiente,
paralelizar el bucle exterior solo es la mejor manera de maximizar las ventajas de simultaneidad en la
mayora de los sistemas.
Delegado
El tercer parmetro de esta sobrecarga de For es un delegado de tipo Action<int> en C# o Action(Of
Integer) en Visual Basic. Un delegado Action siempre devuelve void, tanto si no tiene parmetros
como si tiene uno o diecisis. En Visual Basic, el comportamiento de Action se define con Sub. En el
ejemplo se utiliza una expresin lambda para crear el delegado, pero tambin se puede crear de
otras formas.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 5 de 107
Valor de iteracin
El delegado toma un nico parmetro de entrada cuyo valor es la iteracin actual. El runtime
proporciona este valor de iteracin y su valor inicial es el ndice del primer elemento del segmento
(particin) del origen que se procesa en el subproceso actual.
Si requiere ms control sobre el nivel de simultaneidad, utilice una de las sobrecargas que toma un
parmetro de entrada System.Threading.Tasks.ParallelOptions, como: Parallel.For(Int32, Int32,
ParallelOptions, Action(Of 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 o se interrumpe la iteracin del bucle
de forma manual, ya que ParallelLoopResult almacena informacin como la ltima iteracin que se
ejecut hasta finalizar. Si se producen una o ms excepciones en uno de los subprocesos, se inicia
System.AggregateException.
En el cdigo de este ejemplo, no se usa el valor devuelto de For.
Anlisis y rendimiento
Puede utilizar el Asistente de rendimiento para ver el uso de la CPU en el equipo. Como experimento,
aumente el nmero de columnas y filas en las matrices. Cuanto mayores son las matrices, mayor es la
diferencia de rendimiento entre las versiones en paralelo y en serie del clculo. Si la matriz es
pequea, la versin en serie se ejecutar ms rpidamente debido a la sobrecarga de la
configuracin del bucle paralelo.
Las llamadas sincrnicas a los recursos compartidos, como la consola o el sistema de archivos,
degradarn de forma significativa el rendimiento de un bucle paralelo. Al medir el rendimiento,
intente evitar llamadas como Console.WriteLine dentro del bucle.

1.1.2. Cmo: Escribir un bucle Parallel.ForEach simple
En este ejemplo, se muestra cmo se usa un bucle Parallel.ForEach para habilitar el paralelismo de
datos en cualquier origen de datos System.Collections.IEnumerable o
System.Collections.Generic.IEnumerable(Of T).
Ejemplo
' How to: Write a Simple Parallel.ForEach Loop
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Drawing
Module ForEachDemo
Sub Main()
' A simple source for demonstration purposes. Modify this path as necessary.
Dim files As String() = System.IO.Directory.GetFiles("C:\Users\Public\Pictures\Sample Pictures", "*.jpg")
Dim newDir As String = "C:\Users\Public\Pictures\Sample Pictures\Modified"
System.IO.Directory.CreateDirectory(newDir)
' Method signature: Parallel.ForEach(IEnumerable<TSource> source, Action<TSource> body)
' Be sure to add a reference to System.Drawing.dll.
Parallel.ForEach(files, Sub(currentFile)
' The more computational work you do here, the greater
' the speedup compared to a sequential foreach loop.
Dim filename As String = System.IO.Path.GetFileName(currentFile)
Dim bitmap As New System.Drawing.Bitmap(currentFile)
bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone)
bitmap.Save(System.IO.Path.Combine(newDir, filename))
' Peek behind the scenes to see how work is parallelized.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 6 de 107
' But be aware: Thread contention for the Console slows down parallel loops!!!
Console.WriteLine("Processing {0} on thread {1}", filename,
Thread.CurrentThread.ManagedThreadId)
'close lambda expression and method invocation
End Sub)
' Keep the console window open in debug mode.
Console.WriteLine("Processing complete. Press any key to exit.")
Console.ReadKey()
End Sub
End Module
Un ForEach trabajos del bucle como un bucle For. Se crea particin la recopilacin del origen y el
trabajo se programa en varios subprocesos basados en el entorno del sistema. Cuantos ms
procesadores tenga el sistema, ms rpido se ejecutar el mtodo paralelo. En algunas colecciones
de origen, puede resultar ms rpido un bucle secuencial, en funcin del tamao del origen y del tipo
de trabajo que se realice.
Para utilizar ForEach con una recopilacin no genrica, puede utilizar el mtodo de extensin Cast(Of
TResult) para convertir la recopilacin en una recopilacin genrica, como se muestra en el siguiente
ejemplo:
Parallel.ForEach(nonGenericCollection.Cast(Of Object), _
Sub(currentElement)
' ... work with currentElement
End Sub)
Tambin puede utilizar LINQ Paralelo (PLINQ) para paralelizar procesamiento de origenes de datos
IEnumerable(Of T). PLINQ permite usar una sintaxis de consulta declarativa para expresar el
comportamiento del bucle.

1.1.3. Cmo: Detener o interrumpir un bucle Parallel.For
En el siguiente ejemplo se muestra cmo interrumpir un bucle For (o Salir de l en Visual Basic) y
tambin cmo detener un bucle. En este contexto, "interrumpir" significa completar todas las
iteraciones en todos los subprocesos que son anteriores a la iteracin actual en el subproceso actual
y, a continuacin, salir del bucle. " Pausa" significa detener todas las iteraciones en cuanto
conveniente.
Ejemplo
En este ejemplo se muestra un bucle For; sin embargo, se puede detener o interrumpir desde un
bucle ForEach de la misma manera. En un bucle ForEach, se genera un ndice de iteracin
internamente para cada uno de los elementos de cada particin.
' How to: Stop or Break from a Parallel.For Loop
Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks
Module ParallelForStop
Sub Main()
StopLoop()
BreakAtThreshold()
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Sub StopLoop()
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 7 de 107
Console.WriteLine("Stop loop...")
Dim source As Double() = MakeDemoSource(1000, 1)
Dim results As New ConcurrentStack(Of Double)()
' i is the iteration variable. loopState is a compiler-generated ParallelLoopState
Parallel.For(0, source.Length, Sub(i, loopState)
' Take the first 100 values that are retrieved from anywhere in the source.
If i < 100 Then
' Accessing shared object on each iteration is not efficient. See remarks.
Dim d As Double = Compute(source(i))
results.Push(d)
Else
loopState.[Stop]()
Exit Sub
End If
' Close lambda expression.
End Sub)
' Close Parallel.For
Console.WriteLine("Results contains {0} elements", results.Count())
End Sub
Sub BreakAtThreshold()
Dim source As Double() = MakeDemoSource(10000, 1.0002)
Dim results As New ConcurrentStack(Of Double)()
' Store all values below a specified threshold.
Parallel.For(0, source.Length, Function(i, loopState)
Dim d As Double = Compute(source(i))
results.Push(d)
If d > 0.2 Then
' Might be called more than once!
loopState.Break()
Console.WriteLine("Break called at iteration {0}. d = {1} ", i, d)
Thread.Sleep(1000)
End If
Return d
End Function)
Console.WriteLine("results contains {0} elements", results.Count())
End Sub
Function Compute(ByVal d As Double) As Double
'Make the processor work just a little bit.
Return Math.Sqrt(d)
End Function
' Create a contrived array of monotonically increasing' values for demonstration purposes.
Function MakeDemoSource(ByVal size As Integer, ByVal valToFind As Double) As Double()
Dim result As Double() = New Double(size - 1) {}
Dim initialval As Double = 0.01
For i As Integer = 0 To size - 1
initialval *= valToFind
result(i) = initialval
Next
Return result
End Function
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 8 de 107
End Module
En un bucle ParallelFor u [Overload:System.Threading.Tasks.Parallel.Parallel.ForEach`1], no se puede
usar la misma instruccin break o Exit que se utiliza en un bucle secuencial porque estas
construcciones de lenguaje son vlidas para los bucles, y un "bucle" paralelo es realmente un
mtodo, no un bucle. En su lugar, se usan los mtodos Break o Stop. Algunas de las sobrecargas de
Parallel.For aceptan Action<int, ParallelLoopState> (Action(Of Integer, ParallelLoopState) en Visual
Basic) como parmetro de entrada. El runtime crea en segundo plano el objeto ParallelLoopState, al
que puede dar cualquier nombre que desee en la expresin lambda.
En el siguiente ejemplo, el mtodo requiere slo 100 valores de la secuencia de origen y no importa
qu elementos se recuperan. En este caso se usa el mtodo Stop, porque indica todas las iteraciones
del bucle (incluidas las que comenzaron antes de la iteracin actual en otros subprocesos), para
detenerse en cuanto sea conveniente.
En el segundo mtodo se recuperan todos los elementos hasta un ndice especificado en la secuencia
de origen. En este caso, se llama a Break, porque cuando se llega al ndice en un subproceso, es
posible que todava no se hayan procesado los elementos anteriores en el origen. La interrupcin
har que otros subprocesos abandonen el trabajo en segmentos posteriores (si estn ocupados en
alguno) y que completen el procesamiento de todos los elementos anteriores antes de salir del bucle.
Es importante entender que despus de llamar a Stop o Break, otros subprocesos en un bucl e
pueden seguir ejecutndose durante algn tiempo, pero esto no est bajo el control del
desarrollador de la aplicacin. Puede usar la propiedad ParallelLoopState.IsStopped para comprobar
si el bucle se ha detenido en otro subproceso. En el siguiente ejemplo, si IsStopped es true, no se
escriben ms datos en la coleccin.

1.1.4. Cmo: Escribir un bucle Parallel.For que tenga variables locales de
subproceso
En este ejemplo se muestra cmo utilizar variables locales de subproceso para almacenar y recuperar
el estado de cada tarea independiente que se crea en un bucle For. Si se usan datos locales de
subproceso, se puede evitar la sobrecarga de sincronizar un nmero grande de accesos al estado
compartido. En lugar de escribir en un recurso compartido en cada iteracin, calcula y almacena el
valor hasta que se completan todas las iteraciones de la tarea. A continuacin, puede escribir el
resultado final una vez en el recurso compartido o pasarlo a otro mtodo.
Ejemplo
'How to: Write a Parallel.For Loop That Has Thread-Local Variables
Imports System.Threading
Imports System.Threading.Tasks
Module ForWithThreadLocal
Sub Main()
Dim nums As Integer() = Enumerable.Range(0, 1000000).ToArray()
Dim total As Long = 0
' Use type parameter to make subtotal a Long type. Function will overflow otherwise.
Parallel.For(Of Long)(0, nums.Length, Function() 0, Function(j, [loop], subtotal)
subtotal += nums(j)
Return subtotal
End Function, Function(x) Interlocked.Add(total, x))
Console.WriteLine("The total is {0}", total)
Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub
End Module
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 9 de 107
Los dos primeros parmetros de cada mtodo For especifican los valores de iteracin inicial y final.
En esta sobrecarga del mtodo, el tercer parmetro es donde inicializa el estado local. " Estado local"
en este contexto significa una variable cuya duracin se extiende desde inmediatamente antes de la
primera iteracin del bucle en el subproceso actual hasta inmediatamente despus de la ltima
iteracin.
El tipo del tercer parmetro es Func(Of TResult) donde TResult es el tipo de la variable que
almacenar el estado subproceso local. Observe que en este ejemplo, una versin genrica del
mtodo se utiliza, el parmetro de and type es largo (Long en Visual Basic) El parmetro de tipo
indica el tipo de la variable temporal que se utilizar para almacenar el estado subproceso local al
compilador. La expresin () => 0 (Function() 0 en Visual Basic) en este ejemplo significa que la
variable subproceso local se inicializa para poner a cero. Si el parmetro de tipo es un tipo de
referencia o un tipo de valor definido por el usuario, este ejemplo de Func se parecera al siguiente:
Function() new MyClass()
El cuarto parmetro de tipo es donde define la lgica del bucle. Las presentaciones de IntelliSense
que tiene un tipo de Func<int, ParallelLoopState, long, long> o Func(Of Integer, ParallelLoopState,
Long, Long). La expresin lambda espera tres parmetros de entrada en este mismo orden que
corresponde a estos tipos. El ltimo parmetro de tipo es el tipo devuelto. En este caso, el tipo es
long porque es lo que se especific en el parmetro de tipo For. Llamamos a esa variable subtotal en
la expresin lambda y la devolvemos. El valor devuelto se utiliza para inicializar el subtotal en cada
iteracin subsiguiente. Tambin puede considerar este ltimo parmetro simplemente como un valor
que se pasa a cada iteracin y despus al delegado localFinally cuando se completa la ltima
iteracin.
El quinto parmetro es donde define el mtodo que se denominar un tiempo, cuando todas las
iteraciones en este subproceso hayan completado. El tipo del parmetro de entrada corresponde de
nuevo al parmetro de tipo del mtodo For y al tipo que devuelve la expresin lambda del cuerpo. En
este ejemplo, el 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 en esta variable de
clase en cada iteracin de cada subproceso.

1.1.5. Cmo: Escribir un bucle Parallel.ForEach que tenga variables
locales de subproceso
En el siguiente ejemplo se muestra cmo escribir un mtodo ForEach que utiliza variables locales de
subproceso. Cuando un bucle ForEach se ejecuta, divide su coleccin de origen en varias particiones.
Cada particin obtendr su propia copia de la variable "local de subproceso". (El trmino "local de
subproceso" es ligeramente inexacto, porque en algunos casos dos particiones se pueden ejecutar en
el mismo subproceso).
El cdigo y los parmetros de este ejemplo se parecen mucho al mtodo For correspondiente.
Ejemplo
Para utilizar una variable local de subproceso en un bucle ForEach, debe utilizar la versin del
mtodo que toma dos parmetros type. El primer parmetro especifica el tipo del elemento de
origen y el segundo parmetro especifica el tipo de la variable local de subproceso.
El primer parmetro de entrada es el origen de datos y el segundo es la funcin que inicializar la
variable local de subproceso. El tercer parmetro de entrada es un Func(Of T1, T2, T3, TResult) que
invoca el bucle paralelo en cada iteracin. Se proporciona el cdigo para el delegado y el bucle pasa
los parmetros de entrada. Los parmetros de entrada son el elemento vigente, una variable
ParallelLoopState que permite examinar el estado del bucle, y la variable local de subproceso.
Devuelve la variable local de subproceso y, a continuacin, el mtodo pasa a la iteracin siguiente de
esta particin. Esta variable es distinta en todas las particiones del bucle.
El ltimo parmetro de entrada del mtodo ForEach es el delegado Action(Of T) que el mtodo
invocar cuando todos los bucles se hayan completado. El mtodo proporciona el valor final de la
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 10 de 107
variable local de subproceso para este subproceso (o particin del bucle) y proporciona el cdigo
que captura el valor final y realiza cualquier accin necesaria para combinar el resultado de esta
particin con los resultados de las otras particiones. Como el tipo de delegado es Action(Of T), no
hay valor devuelto.
' How to: Write a Parallel.ForEach Loop That Has Thread-Local Variables
Imports System.Threading
Imports System.Threading.Tasks
Module ForEachThreadLocal
Sub Main()
Dim nums() As Integer = Enumerable.Range(0, 1000000).ToArray()
Dim total As Long = 0
' First type paramemter is the type of the source elements
' Second type parameter is the type of the local data (subtotal)
Parallel.ForEach(Of Integer, Long)(nums, Function() 0,
Function(elem, loopState, subtotal)
subtotal += nums(elem)
Return subtotal
End Function,
Sub(finalResult)
Interlocked.Add(total, finalResult)
End Sub)
Console.WriteLine("The result of Parallel.ForEach is {0}", total)
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
End Module

1.1.6. Cmo: Cancelar un bucle Parallel.For o ForEach
Los mtodos Parallel.ForEach y Parallel.For admiten la cancelacin a travs del uso de tokens de
cancelacin. En un bucle paralelo, se proporciona CancellationToken al mtodo en el parmetro
ParallelOptions y despus se agrega la llamada paralela en un bloque try-catch.
Ejemplo
En el ejemplo siguiente se muestra cmo cancelar una llamada a Parallel.ForEach. Puede aplicar el
mismo enfoque a una llamada Parallel.For.
' How to: Cancel a Parallel.For or ForEach Loop
Imports System.Threading
Imports System.Threading.Tasks
Module CancelParallelLoops
Sub Main()
Dim nums() As Integer = Enumerable.Range(0, 10000000).ToArray()
Dim cts As New CancellationTokenSource
' Use ParallelOptions instance to store the CancellationToken
Dim po As 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.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 11 de 107
Dim t As Task = Task.Factory.StartNew(Sub()
If Console.ReadKey().KeyChar = "c"c Then
cts.Cancel()
End If
Console.WriteLine(vbCrLf & "Press any key to exit.")
End Sub)
Try
' The error "Exception is unhandled by user code" will appear if "Just My Code"
' is enabled. This error is benign. You can press F5 to continue, or disable Just My Code.
Parallel.ForEach(nums, po, Sub(num)
Dim d As Double = Math.Sqrt(num)
Console.CursorLeft = 0
Console.Write("{0:##.##} on {1}", d, Thread.CurrentThread.ManagedThreadId)
po.CancellationToken.ThrowIfCancellationRequested()
End Sub)
Catch e As OperationCanceledException
Console.WriteLine(e.Message)
End Try
Console.ReadKey()
End Sub
End Module
Si el token que seala la cancelacin es el mismo que se especifica en la instancia de ParallelOptions,
el bucle paralelo producir una OperationCanceledException nica en la cancelacin. Si algn otro
token produce la cancelacin, el bucle producir una AggregateException con
OperationCanceledException como InnerException.

1.1.7. Cmo: Controlar excepciones en bucles paralelos
El ParallelFory las sobrecargas ForEach no tienen ningn mecanismo especial para administrar
excepciones que se podran producir. A este respecto, se asemejan a bucles for y foreach normales
(For y For Each en Visual Basic).
Cuando agregue su propia lgica de control de excepciones a los bucles paralelos, tenga en cuenta la
posibilidad de que se inicien excepciones similares en varios subprocesos al mismo tiempo, as como
el caso de que una excepcin iniciada en un subproceso puede hacer que se inicie otra excepcin en
otro subproceso. Puede administrar ambos casos si encapsula todas las excepciones del bucle en
System.AggregateException. En el ejemplo siguiente se muestra un posible enfoque.
Nota
Cuando est habilitada la opcin "Solo mi cdigo", en algunos casos, Visual Studio se interrumpe en
la lnea que produce la excepcin y muestra el mensaje de error "Excepcin no controlada por el
cdigo de usuario". Este error es benigno. Puede presionar F5 para continuar y ver el
comportamiento de control de excepciones que se muestra en el ejemplo siguiente. Para evitar que
Visual Studio se interrumpa con el primer error, desactive la casilla "Solo mi cdigo" bajo
Herramientas, Opciones, Depuracin, General.
Ejemplo
En este ejemplo, todas las excepciones se detectan y, a continuacin, se encapsulan en la excepcin
System.AggregateException que se produce. El llamador puede decidir qu excepciones se deben
administrar.
' How to: Handle Exceptions in Parallel Loops
Imports System.Collections.Concurrent
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 12 de 107
Imports System.Threading.Tasks
Module ExceptionsInLoops
Sub Main()
' Create some random data to process in parallel.
' There is a good probability this data will cause some exceptions to be thrown.
Dim data(1000) As Byte
Dim r As New Random()
r.NextBytes(data)
Try
ProcessDataInParallel(data)
Catch ae As AggregateException
' This is where you can choose which exceptions to handle.
For Each ex As Exception In ae.InnerExceptions
If (TypeOf (ex) Is ArgumentException) Then
Console.WriteLine(ex.Message)
Else
Throw ex
End If
Next
End Try
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Sub ProcessDataInParallel(ByVal data As Byte())
' Use ConcurrentQueue to enable safe enqueueing from multiple threads.
Dim exceptions As New ConcurrentQueue(Of Exception)
' Execute the complete loop and capture all exceptions.
Parallel.ForEach(Of Byte)(data, Sub(d)
Try
' Cause a few exceptions, but not too many.
If d < &H3 Then
Throw New ArgumentException(String.Format("value is {0:x}. Element must be greater than
&H3", d))
Else
Console.Write(d & " ")
End If
Catch ex As Exception
' Store the exception and continue with the loop.
exceptions.Enqueue(ex)
End Try
End Sub)
' Throw the exceptions here after the loop completes.
If exceptions.Count > 0 Then
Throw New AggregateException(exceptions)
End If
End Sub
End Module

1.1.8 Cmo: Acelerar cuerpos de bucle pequeos
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 13 de 107
Cuando un bucle For tiene un cuerpo pequeo, puede registrar un rendimiento ms lento que el del
bucle secuencial equivalente. Este rendimiento ms lento es consecuencia de la sobrecarga en la
participacin de los datos y el costo de invocar un delegado en cada iteracin del bucle. Para hacer
frente a estos escenarios, la clase Partitioner proporciona el mtodo Create, que permite
proporcionar un bucle secuencial para el cuerpo de delegado de modo que el delegado solo se
invoque una vez por particin, en lugar de una vez por iteracin.
Ejemplo
Imports System.Threading.Tasks
Imports System.Collections.Concurrent
Module PartitionDemo
Sub Main()
' Source must be array or IList.
Dim source = Enumerable.Range(0, 100000).ToArray()
' Partition the entire source array.
' Let the partitioner size the ranges.
Dim rangePartitioner = Partitioner.Create(0, source.Length)
Dim results(source.Length - 1) As Double
' Loop over the partitions in parallel. The Sub is invoked once per partition.
Parallel.ForEach(rangePartitioner, Sub(range, loopState)
' Loop over each range element without a delegate invocation.
For i As Integer = range.Item1 To range.Item2 - 1
results(i) = source(i) * Math.PI
Next
End Sub)
Console.WriteLine("Operation complete. Print results? y/n")
Dim input As Char = Console.ReadKey().KeyChar
If input = "y"c Or input = "Y"c Then
For Each d As Double In results
Console.Write("{0} ", d)
Next
End If
End Sub
End Module
El enfoque mostrado en este ejemplo es til cuando el bucle realiza una cantidad de trabajo mnimo.
Cuando el trabajo se vuelve computationally caro, probablemente obtendr el rendimiento mismo o
mejor utilizando For o bucle ForEach con el partitioner predeterminado.

1.1.9. Cmo: Recorrer en iteracin directorios con la clase paralela
En muchos casos, la iteracin del archivo es una operacin que se puede paralelizar con facilidad.
Cmo: Recorrer en iteracin directorios con PLINQ del tema muestra la manera ms fcil de realizar
esta tarea para muchos escenarios. Sin embargo, las complicaciones pueden surgir cuando su cdigo
tiene que repartir con el muchos tipos de excepciones que pueden surgir al tener acceso al sistema
de archivos. En el siguiente ejemplo se muestra un enfoque al problema. Utiliza una iteracin basada
en pila para atravesar todos los archivos y carpetas bajo un directorio especificado y habilita su
cdigo para detectar y administrar varias excepciones. Claro, la manera que administra las
excepciones depende de usted.
Ejemplo
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 14 de 107
En el siguiente ejemplo la iteracin sobre los directorios se realiza secuencialmente, pero el
procesamiento de los archivos se hace en paralelo. ste probablemente es el enfoque mejor al tener
una proporcin archivo a directorio grande. Tambin es posible paralelizar la iteracin del directorio
y tiene acceso secuencialmente a cada archivo. Probablemente no es eficaz para paralelizar ambos
bucles a menos que est destinando especficamente un equipo con un nmero grande de
procesadores. Sin embargo, como en todos los casos, debera probar completamente su aplicacin
para determinar el enfoque mejor.
Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.IO
Imports System.Linq
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks
Module Parallel_File
Sub Main(ByVal args() As String)
TraverseTreeParallelForEach("C:\Program Files", Sub(f)
' For this demo we don't do anything with the data
' except to read it.
Dim data() As Byte = File.ReadAllBytes(f)
' For user interest, although it slows down the operation.
Console.WriteLine(f)
End Sub)
' Keep the console window open.
Console.ReadKey()
End Sub
Public Sub TraverseTreeParallelForEach(ByVal root As String, ByVal action As Action(Of String))
'Count of files traversed and timer for diagnostic output
Dim fileCount As Integer = 0
Dim sw As Stopwatch = Stopwatch.StartNew()
' Use this value to determine whether to parallelize
' file processing on each folder.
Dim procCount As Integer = System.Environment.ProcessorCount
' Data structure to hold names of subfolders to be
' examined for files.
Dim dirs As Stack(Of String) = New Stack(Of String)
If System.IO.Directory.Exists(root) = False Then
Throw New ArgumentException()
End If
dirs.Push(root)
While (dirs.Count > 0)
Dim currentDir As String = dirs.Pop()
Dim subDirs() As String = Nothing
Dim files() As String = Nothing
Try
subDirs = System.IO.Directory.GetDirectories(currentDir)
' An UnauthorizedAccessException exception will be thrown if we do not have discovery permission on a
folder or
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 15 de 107
' file. It may or may not be acceptable 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 e As UnauthorizedAccessException
Console.WriteLine(e.Message)
Continue While
Catch e As System.IO.DirectoryNotFoundException
Console.WriteLine(e.Message)
Continue While
End Try
Try
files = System.IO.Directory.GetFiles(currentDir)
Catch e As UnauthorizedAccessException
Console.WriteLine(e.Message)
Continue While
Catch e As System.IO.DirectoryNotFoundException
Console.WriteLine(e.Message)
Continue While
End Try
' 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 Then
For Each file In files
action(file)
fileCount = fileCount + 1
Next
Else
Parallel.ForEach(files, Function() 0, Function(file, loopState, localCount)
action(file)
localCount = localCount + 1
Return CType(localCount, Integer)
End Function,
Sub(c)
Interlocked.Exchange(fileCount, fileCount + c)
End Sub)
End If
Catch ae As AggregateException
ae.Handle(Function(ex)
If TypeOf (ex) Is UnauthorizedAccessException Then
' Here we just output a message and go on.
Console.WriteLine(ex.Message)
Return True
End If
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 16 de 107
' Handle other exceptions here if necessary...
Return False
End Function)
End Try
' Push the subdirectories onto the stack for traversal. This could also be done before handing the files.
For Each str As String In subDirs
dirs.Push(str)
Next
' For diagnostic purposes.
Console.WriteLine("Processed {0} files in {1} milleseconds", fileCount, sw.ElapsedMilliseconds)
End While
End Sub
End Module
En este ejemplo, el archivo que sincrnicamente se realiza E/S. Al tratar con archivos grandes o
conexiones de red lentas, podra ser preferible para tener acceso de forma asincrnica a los archivos.
Puede combinar las tcnicas E/S asincrnico con iteracin paralela.
Tenga en cuenta que si una excepcin se produce en el subproceso principal, los subprocesos que se
son iniciados por el mtodo ForEach podran continuar ejecutndose. Para detener estos
subprocesos, puede establecer una variable Boolean en sus controladores de excepciones y
comprueba su valor en cada iteracin del bucle paralelo. Si el valor indica que se ha producido una
excepcin, utilice la variable ParallelLoopState para detenerse o interrumpir del bucle.

1.2. Paralelismo de tareas (Task Parallel Library)
Como indica su nombre, la biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo
basado en tareas) se basa en el concepto de tarea ("task" en ingls). El paralelismo de tarea de
trmino hace referencia a una o ms tareas independientes que se ejecutan concurrentemente. Una
tarea representa una operacin asincrnica y, en ciertos aspectos, se asemeja a la creacin de un
nuevo subproceso o elemento de trabajo ThreadPool, pero con un nivel de abstraccin mayor. Las
tareas proporcionan dos ventajas fundamentales:
Un uso ms eficaz y ms 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 de colina o "hill-climbing") que
determinan y ajustan el nmero de subprocesos con el que se maximiza el rendimiento. Esto
hace que las tareas resulten relativamente ligeras y que, por tanto, pueda crearse un gran
nmero de ellas para habilitar un paralelismo pormenorizado. Como complemento y para
proporcionar el equilibrio de carga, se usan los conocidos algoritmos de robo de trabajo.
Un mayor control mediante programacin 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, programacin personalizada, y ms.
Por estos dos motivos, en .NET Framework 4, las tareas son las API preferidas para escribir cdigo
paralelo, multiproceso y asincrnico.
Crear y ejecutar tareas implcitamente
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 17 de 107
El mtodo Parallel.Invoke proporciona una manera conveniente de ejecutar cualquier nmero de
instrucciones arbitrarias simultneamente. Pase un delegado Action por cada elemento de trabajo. La
manera ms fcil de crear estos delegados es con expresiones lambda. La expresin lambda puede
llamar a un mtodo con nombre o proporcionar el cdigo alineado. En el siguiente ejemplo se
muestra una llamada a Invoke bsica que crea e inicia dos tareas que se ejecutan a la vez.
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())
El nmero de instancias de Task que Invoke crea en segundo plano no es necesariamente igual al
nmero de delegados que se proporcionan. La TPL puede emplear varias optimizaciones, sobre todo
con grandes nmeros de delegados.
Crear y ejecutar tareas explcitamente
Una tarea se representa mediante la clase System.Threading.Tasks.Task. Una tarea que devuelve un
valor se representa mediante la clase System.Threading.Tasks.Task(Of TResult), que se hereda de
Task. El objeto de tarea administra los detalles de la infraestructura y proporciona mtodos y
propiedades a los que se puede obtener acceso desde el subproceso que realiza la llamada a lo largo
de la duracin de la tarea. Por ejemplo, se puede tener acceso a la propiedad Status de una tarea en
cualquier momento para determinar si ha empezado a ejecutarse, si se ha ejecutado hasta su
finalizacin, si se ha cancelado o si se ha producido una excepcin. El estado se representa mediante
la enumeracin TaskStatus.
Cuando se crea una tarea, se proporciona un delegado de usuario que encapsula el cdigo que la
tarea va a ejecutar. El delegado se puede expresar como un delegado con nombre, un mtodo
annimo o una expresin lambda. Las expresiones lambda pueden contener una llamada a un
mtodo con nombre, tal y como se muestra en el siguiente ejemplo.
' Create a task and supply a user delegate by using a lambda expression.
Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
' Start the task.
taskA.Start()
' Output a message from the joining thread.
Console.WriteLine("Hello from the joining thread.")
' Output:
' Hello from the joining thread.
' Hello from taskA.
Tambin se puede usar el mtodo StartNew para crear e iniciar una tarea en una sola operacin. sta
es la manera preferida para crear e iniciar las tareas si la creacin y programar no tiene que ser
separadose, como se muestra en el siguiente ejemplo
' Better: Create and start the task in one operation.
Dim taskA = Task.Factory.StartNew(Sub() Console.WriteLine("Hello from taskA."))
' Output a message from the joining thread.
Console.WriteLine("Hello from the joining thread.")
Tarea expone una propiedad Factory esttica que devuelve una instancia predeterminada de
TaskFactory, para que pueda llamar al mtodo como Task.Factory.StartNew(). Asimismo, en este
ejemplo, dado que las tareas son de tipo System.Threading.Tasks.Task(Of TResult), cada una tiene
una propiedad Result pblica que contiene el resultado del clculo. Las tareas se ejecutan de forma
asincrnica y pueden completarse en cualquier orden. Si se tiene acceso Result antes de que el
clculo complete, la propiedad bloquear el subproceso hasta que el valor est disponible.
Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation1()),
Task(Of Double).Factory.StartNew(Function() DoComputation2()),
Task(Of Double).Factory.StartNew(Function() DoComputation3())}
Dim results() As Double
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 18 de 107
ReDim results(taskArray.Length)
For i As Integer = 0 To taskArray.Length
results(i) = taskArray(i).Result
Next
Al utilizar una expresin lambda para crear el delegado de una tarea, tiene el acceso a todas las
variables que estn visibles en ese punto en su cdigo fuente. Sin embargo, en algunos casos, un
lambda no captura notablemente dentro de los bucles, la variable como usted podra esperar. Slo
captura el valor final, no el valor cuando deforma despus de cada iteracin. Puede tener acceso al
valor en cada iteracin proporcionando un objeto de estados a una tarea a travs de su constructor,
como se muestra en el siguiente ejemplo:
Class MyCustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class
Sub 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 lamda,
' and compare results.
Dim taskArray() As Task
ReDim taskArray(10)
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = New Task(Sub(obj As Object)
Dim mydata = CType(obj, MyCustomData)
mydata.ThreadNum = Thread.CurrentThread.ManagedThreadId
Console.WriteLine("Hello from Task #{0} created at {1} running on thread #{2}.",
mydata.Name, mydata.CreationTime, mydata.ThreadNum)
End Sub,
New MyCustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks} )
taskArray(i).Start()
Next
End Sub
Se pasa por este estado como un argumento al delegado de tarea y es accesible del objeto de tarea
utilizando la propiedad AsyncState. Al pasar los datos a travs del constructor, tambin, se podra
proporcionar una ventaja de rendimiento pequea en algunos escenarios.
Identificador de tarea
Cada tarea recibe un identificador entero que la identifica de manera inequvoca en un dominio de
aplicacin y al que se puede obtener acceso mediante la propiedad Id. El identificador resulta til
para ver informacin sobre la tarea en las ventanas Pilas paralelas y Tareas paralelas del depurador
de Visual Studio. El identificador se crea de forma diferida, lo que significa que no se crea hasta que
se solicita; por tanto, una tarea podr tener un identificador diferente cada vez que se ejecute el
programa.
Opciones de creacin de tareas
La mayora de las API que crean las tareas proporcionan sobrecargas que aceptan un parmetro
TaskCreationOptions. Al especificar una de estas opciones, se le est indicando al programador cmo
se programa la tarea en el grupo de subprocesos. En la tabla siguiente se muestran las diversas
opciones de creacin de tareas.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 19 de 107
Elemento Descripcin
None
Es la opcin predeterminada si no se especifica ninguna opcin. El programador
usa su heurstica predeterminada para programar la tarea.
PreferFairness
Especifica que la tarea debe programarse de modo que las tareas creadas
anteriormente tengan ms posibilidades de ejecutarse antes y que las tareas
posteriormente tengan ms posibilidades de ejecutarse despus.
LongRunning Especifica que la tarea representa una operacin de ejecucin prolongada.
AttachedToParent
Especifica que una tarea debe crearse como un elemento secundario asociado de
la tarea actual, si existe.
Las opciones pueden combinarse con una operacin OR bit a bit. En el siguiente ejemplo se muestra
una tarea que tiene LongRunning y opcin PreferFairness.
Dim task3 = New Task(Sub() MyLongRunningMethod(),
TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()
Crear continuaciones de tareas
El mtodo Task.ContinueWith y el mtodo Task(Of TResult).ContinueWith permiten especificar que
una tarea se inicie cuando la tarea anterior se complete. Al delegado de la tarea de continuacin se le
pasa una referencia de la tarea anterior para que pueda examinar su estado. Adems, la tarea de
continuacin puede 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 de continuacin. En el
ejemplo siguiente, el cdigo del programa inicia getData; a continuacin, se inicia analyzeData
automticamente cuando getData se completa; por ltimo, reportData se inicia cuando analyzeData
se completa. getData genera como resultado una matriz de bytes, que se pasa a analyzeData.
analyzeData procesa esa matriz y devuelve un resultado cuyo tipo se infiere del tipo devuelto del
mtodo Analyze. reportData toma la entrada de analyzeData y genera un resultado cuyo tipo se
infiere de forma similar y que se pasa a estar disponible en el programa en la propiedad Result.
Dim getData As Task(Of Byte()) = New Task(Of Byte())(Function() GetFileData())
Dim analyzeData As Task(Of Double()) = getData.ContinueWith(Function(x) Analyze(x.Result))
Dim reportData As Task(Of String) = analyzeData.ContinueWith(Function(y As Task(Of Double))
Summarize(y.Result))
getData.Start()
System.IO.File.WriteAllText("C:\reportFolder\report.txt", reportData.Result)
Los mtodos ContinueWhenAny y ContinueWhenAll le permiten continuar en varias tareas.
Crear tareas anidadas desasociadas
Cuando el cdigo de usuario que se est ejecutando en una tarea crea una nueva tarea y no
especifica la opcin AttachedToParent, la nueva tarea no sincronizada con la tarea exterior de
cualquier manera especial. Tales tareas se llaman una tarea anidada aislada. En el siguiente ejemplo
se muestra una tarea que crea una tarea anidada desasociada.
Dim outer = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task beginning.")
Dim child = Task.Factory.StartNew(Sub()
Thread.SpinWait(5000000)
Console.WriteLine("Detached task completed.")
End Sub)
End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 20 de 107
' Output:
' Outer task beginning.
' Outer task completed.
' Detached child completed.
Observe que la tarea externa no espera a que la tarea anidada se complete.
Crear tareas secundarias
Cuando el cdigo de usuario que se est ejecutando en una tarea crea una tarea con la opcin
AttachedToParent, la nueva tarea es conocido como una tarea secundaria de la tarea para originar,
que es conocido como la tarea primaria. Puede usar la opcin AttachedToParent para expresar el
paralelismo de tareas estructurado, ya que la tarea primaria espera implcitamente a que todas las
tareas secundarias se completen. En el siguiente ejemplo se muestra una tarea que crea una tarea
secundaria:
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task beginning.")
Dim child = Task.Factory.StartNew(Sub()
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completed.")
End Sub,
TaskCreationOptions.AttachedToParent)
End Sub)
outer.Wait()
Console.WriteLine("Parent task completed.")
' Output:
' Parent task beginning.
' Attached child completed.
' Parent task completed.
Esperar tareas
El tipo System.Threading.Tasks.Task y el tipo System.Threading.Tasks.Task(Of TResult) proporcionan
varias sobrecargas del mtodo Task(Of TResult).Wait y Task.Wait que le permiten esperar para que
una tarea complete. Adems, las sobrecargas del mtodo Task.WaitAll esttico y del mtodo
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.
En el siguiente ejemplo se muestra el modelo bsico donde el control de excepciones no est
implicado.
Dim tasks() =
{
Task.Factory.StartNew(Sub() MethodA()),
Task.Factory.StartNew(Sub() MethodB()),
Task.Factory.StartNew(Sub() MethodC())
}
' Block until all tasks complete.
Task.WaitAll(tasks)
' Continue on this thread...
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 21 de 107
Algunas sobrecargas permiten especificar un tiempo de espera, mientras que otras toman un objeto
CancellationToken adicional como parmetro de entrada, de modo que la espera puede cancelarse
mediante programacin o en respuesta a los datos proporcionados por el usuario.
Cuando se espera a una tarea, se espera implcitamente a todos los elementos secundarios de esa
tarea que se crearon con la opcin AttachedToParent de TaskCreationOptions. Task.Wait devuelve un
valor inmediatamente si la tarea ya se ha completado. Un mtodo Wait producir las tareas
generadas por una tarea incluso si se llama a este mtodo Wait una vez completada la tarea.
Control de excepciones en tareas
Cuando una tarea produce una o varias excepciones, las excepciones se encapsulan en un objeto
AggregateException. Esa excepcin se propaga de nuevo al 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 la tarea. Este comportamiento sirve para aplicar la directiva de .NET Framework
por la que, de manera predeterminada, todas las excepciones no controladas deben anular el
proceso. El cdigo de llamada puede controlar las excepciones a travs de los mtodos Wait, WaitAll
o WaitAny o de la propiedad Result de la tarea o grupo de tareas, mientras incluye el mtodo Wait
en un bloque try-catch.
El subproceso de unin tambin puede controlar excepciones; para ello, obtiene acceso a la
propiedad Exception antes de que la tarea se recolecte como elemento no utilizado. Al obtener
acceso a esta propiedad, impide que la excepcin no controlada desencadene el comportamiento de
propagacin de la excepcin que anula el proceso cuando el objeto ha finalizado.
Cancelar tareas
La clase Task admite la cancelacin cooperativa y su completa integracin con las clases
System.Threading. CancellationTokenSource y System.Threading.CancellationToken, que son nuevas
en .NET Framework versin 4. Muchos de los constructores de la clase System.Threading.Tasks.Task
toman un objeto CancellationToken como parmetro de entrada. Muchas de las sobrecargas de
StartNew toman tambin CancellationToken.
Puede crear el token y emitir la solicitud de cancelacin posteriormente usando la clase
CancellationTokenSource. A continuacin, debe pasar el token a Task como argumento y hacer
referencia al mismo token tambin en el delegado de usuario, que se encarga de responder a una
solicitud de cancelacin.
La clase TaskFactory
La clase TaskFactory proporciona mtodos estticos que encapsulan algunos modelos comunes de
creacin e inicio de tareas y tareas de continuacin.
El modelo ms comn es StartNew, que crea e inicia una tarea en una sola instruccin.
Al crear las tareas de continuacin a partir de varios antepasados, utilice el mtodo
ContinueWhenAll o el mtodo ContinueWhenAnyo sus equivalentes en la clase Task(Of
TResult).
Para encapsular los mtodos BeginX y EndX del modelo de programacin asincrnica en una
instancia de Task o Task(Of TResult), use los mtodos FromAsync.
El objeto TaskFactory predeterminado es accesible como propiedad esttica de la clase Task o de la
clase Task(Of TResult). Tambin pueden crearse directamente instancias de TaskFactory y especificar
varias opciones entre las que se incluyan las opciones CancellationToken, TaskCreationOptions,
TaskContinuationOptions o TaskScheduler. Cualquier opcin que se especifique al crear el generador
de tareas se aplicar a todas las tareas que este generador cree, a menos que la tarea se cree usando
la enumeracin TaskCreationOptions, en cuyo caso las opciones de la tarea reemplazarn a las del
generador de tareas.
Tareas sin delegados
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 22 de 107
En algunos casos, es posible que desee usar un objeto Task para encapsular alguna operacin
asincrnica ejecutada por un componente externo en lugar de su propio usuario delegado. Si la
operacin se basa en el patrn Begin/End del modelo de programacin asincrnica, puede usar los
mtodos FromAsync. Si no es este el caso, puede usar el objeto TaskCompletionSource(Of TResult)
para encapsular la operacin en una tarea y, de este modo, aprovechar algunas de las ventajas de
programacin de Task, como por ejemplo, su compatibilidad con la propagacin de excepciones y el
uso de continuaciones.
Programadores personalizados
La mayora de los desarrolladores de aplicaciones o bibliotecas no prestan atencin al procesador en
el que se ejecuta la tarea, al modo en que la tarea sincroniza su trabajo con otras tareas o al modo en
que se programa la tarea en el objeto System.Threading.ThreadPool. Solo necesitan que la ejecucin
en el equipo host sea lo ms eficaz posible. Si necesita tener un control ms minucioso sobre los
detalles de programacin, la biblioteca TPL (Task Parallel Library, biblioteca de procesamiento
paralelo basado en tareas) permite configurar algunos valores del programador de tareas
predeterminado e incluso permite proporcionar un programador personalizado.
Estructuras de datos relacionadas
TPL tiene varios tipos pblicos nuevos que resultan tiles tanto en escenarios en paralelo como en
escenarios secuenciales. Entre ellos, se incluyen diversas clases de colecciones multiproceso rpidas y
escalables del espacio de nombres System.Collections.Concurrent y varios tipos nuevos de
sincronizacin, como SemaphoreLock y System.Threading.ManualResetEventSlim, que resultan ms
eficaces que sus predecesores en tipos concretos de cargas de trabajo. Otros tipos nuevos de .NET
Framework versin 4, como System.Threading.Barrier y System.Threading.SpinLock, proporcionan una
funcionalidad que no estaba disponible en versiones anteriores.
Los tipos Task personalizados
Recomendamos no heredar de System.Threading.Tasks.Task o System.Threading.Tasks.Task(Of
TResult). En su lugar, utilice la propiedad AsyncState para asociar los datos adicionales o decir con
objeto Task(Of TResult) o Task. Tambin puede utilizar los mtodos de extensin para extender la
funcionalidad de las clases Task(Of TResult) y Task.
Si debe heredar de Task o Task(Of TResult), no puede utilizar System.Threading.Tasks.TaskFactory, las
clases System.Threading.Tasks.TaskCompletionSource(Of TResult) o
System.Threading.Tasks.TaskFactory(Of TResult)para crear instancias de su tipo de tarea
personalizado porque estas clases slo crean objetos Task(Of TResult) y Task. Adems, no puede
utilizar los mecanismos de continuacin de tarea que son proporcionados por Task, Task(Of TResult),
TaskFactory y TaskFactory(Of TResult) para crear instancias de su tipo de tarea personalizado porque
estos mecanismos tambin crean slo objetos Task(Of TResult) y Task.
1.2.1. Tareas de continuacin
En la programacin asincrnica, es muy comn que una operacin asincrnica, cuando se completa,
invoque una segunda operacin y le pase datos. Tradicionalmente, esto se haca utilizando mtodos
de devolucin de llamada. En la biblioteca TPL (Task Parallel Library, biblioteca de procesamiento
paralelo basado en tareas), las tareas de continuacin proporcionan la misma funcionalidad. Una
tarea de continuacin es una tarea asincrnico (tambin se conoce as como una continuacin) que
es invocada por otra tarea, que es conocido como el antecedente, cuando el antecedente completa.
Las continuaciones son relativamente fciles de usar, y no por ello dejan de ser eficaces y flexibles.
Por ejemplo, puede:
Pasar datos del antecedente a la continuacin
Especificar las condiciones precisas en las que se invocar o no la continuacin
Cancelar una continuacin antes de iniciarse o, de manera cooperativa, mientras se est
ejecutando
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 23 de 107
Proporcionar sugerencias sobre cmo se debera programar la continuacin
Invocar varias continuaciones desde el mismo antecedente
Invocar una continuacin cuando se completa una parte o la totalidad de los antecedentes
Encadenar las continuaciones una tras otra hasta cualquier longitud arbitraria
Usar una continuacin para controlar las excepciones producidas por el antecedente
Las continuaciones se crean con el mtodo Task.ContinueWith. En el siguiente ejemplo se muestra el
modelo bsico (para mayor claridad, se omite el control de excepciones).
' The antecedent task. Can also be created with Task.Factory.StartNew.
Dim taskA As Task(Of DayOfWeek) = New Task(Of DayOfWeek)(Function()
Return DateTime.Today.DayOfWeek
End Function)
' The continuation. Its delegate takes the antecedent task as an argument and can return a different type.
Dim continuation As Task(Of String) = taskA.ContinueWith(Function(antecedent)
Return String.Format("Today is {0}", antecedent.Result)
End Function)
' Start the antecedent.
taskA.Start()
' Use the contuation's result.
Console.WriteLine(continuation.Result)
Tambin puede crear una continuacin de varias tareas que se ejecutar cuando una parte o la
totalidad de las tareas de una matriz de tareas se haya completado, como se muestra en el siguiente
ejemplo.
Dim task1 As Task(Of Integer) = New Task(Of Integer)(Function()
' Do some work...
Return 34
End Function)
Dim task2 As Task(Of Integer) = New Task(Of Integer)(Function()
' Do some work...
Return 8
End Function)
Dim tasks() As Task(Of Integer) = {task1, task2}
Dim continuation = Task.Factory.ContinueWhenAll(tasks, Sub(antecedents)
Dim answer As Integer = tasks(0).Result + tasks(1).Result
Console.WriteLine("The answer is {0}", answer)
End Sub)
task1.Start()
task2.Start()
continuation.Wait()
Una continuacin se crea en el estado WaitingForActivation y, por lo tanto, nicamente puede
iniciarla su tarea antecedente. Al llamar a Task.Start en una continuacin en el cdigo de usuario, se
produce una excepcin System.InvalidOperationException.
Una continuacin se es Task y no bloquea el subproceso en el que se inicia. Utilice el mtodo de
espera para bloquearse hasta que la tarea de continuacin complete.
Opciones de una continuacin
Al crear una continuacin de una sola tarea, puede usar una sobrecarga ContinueWith que tome la
enumeracin System.Threading.Tasks.TaskContinuationOptions para especificar las condiciones en
las que la tarea antecedente debe iniciar la continuacin. Por ejemplo, puede especificar que la
continuacin se ejecute solo si el antecedente se ejecut completamente o solo si se complet con
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 24 de 107
errores, etc. Si no se cumple la condicin cuando el antecedente est listo para invocar la
continuacin, la continuacin pasa directamente al estado Canceled y desde ese momento no se
podr iniciar. Si especifica la opcin NotOn u OnlyOn con una continuacin de varias tareas, se
producir una excepcin en tiempo de ejecucin.
La enumeracin System.Threading.Tasks.TaskContinuationOptions tambin incluye las mismas
opciones que la enumeracin System.Threading.Tasks.TaskCreationOptions. AttachedToParent,
LongRunning y PreferFairness tienen los mismos significados y valores en ambos tipos de
enumeracin. Estas opciones se pueden usar con continuaciones de varias tareas.
En la siguiente tabla se muestran todos los valores de TaskContinuationOptions.
Elemento Descripcin
None
Especifica el comportamiento predeterminado cuando no se especifican
TaskContinuationOptions. La continuacin se programar una vez
completado el antecedente, independientemente del estado final de este.
Si la tarea es una tarea secundaria, se crea como una tarea anidada
desasociada.
PreferFairness
Especifica que la continuacin se programar de modo que las tareas
programadas antes tengan ms posibilidades de ejecutarse antes y las
tareas programadas despus tengan ms posibilidades de ejecutarse ms
tarde.
LongRunning
Especifica que la continuacin ser una operacin general de larga
duracin. Proporciona una sugerencia al
System.Threading.Tasks.TaskScheduler de que se puede garantizar la
sobresuscripcin.
AttachedToParent
Especifica que la continuacin, si es una tarea secundaria, se adjunta a un
elemento primario en la jerarqua de tareas. La continuacin es una tarea
secundaria solo si su antecedente tambin es una tarea secundaria.
NotOnRanToCompletion
Especifica que no se debe programar la continuacin si su antecedente se
ejecuta completamente.
NotOnFaulted
Especifica que no se debe programar la continuacin si su antecedente
produjo una excepcin no controlada.
NotOnCanceled
Especifica que no se debe programar la continuacin si se cancela su
antecedente.
OnlyOnRanToCompletion
Especifica que la continuacin solo se debe programar si el antecedente se
ejecuta completamente.
OnlyOnFaulted
Especifica que la continuacin solo se debe programar si su antecedente
produjo una excepcin no controlada. Al utilizar la opcin OnlyOnFaulted,
se garantiza que la propiedad Exception en el antecedente no es nula.
Puede usar esa propiedad para detectar la excepcin y ver qu excepcin
provoc el error de la tarea. Si no tiene acceso a la propiedad Exception, no
se controlar la excepcin. Asimismo, si intenta tener acceso a la propiedad
Result de una tarea cancelada o con errores, se producir una nueva
excepcin.
OnlyOnCanceled
Especifica que la continuacin debe programarse nicamente si su
antecedente se completa en estado Canceled.
ExecuteSynchronously
Para las continuaciones de muy corta duracin. Especifica que lo ideal es
que la continuacin se ejecute en el mismo subproceso que causa la
transicin del antecedente a su estado final. Si el antecedente ya se ha
completado cuando se crea la continuacin, el sistema intentar ejecutar la
continuacin en el subproceso que la crea. Si CancellationTokenSource del
antecedente se dispone en un bloque finally (Finally en Visual Basic), una
continuacin con esta opcin se ejecutar en ese bloque finally.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 25 de 107
Pasar datos a una continuacin
Una referencia al antecedente se pasa como argumento al delegado de usuario de la continuacin. Si
el antecedente es System.Threading.Tasks.Task(Of TResult) y la tarea se ejecut completamente, la
continuacin puede tener acceso a la propiedad Task(Of TResult).Result de la tarea. Con una
continuacin de varias tareas y el mtodo Task.WaitAll, el argumento es la matriz de antecedentes. Al
usar Task.WaitAny, el argumento es el primer antecedente que se complet.
Task(Of TResult).Result se bloquea hasta que la tarea se ha completado. Sin embargo, si la tarea se
cancel o tiene errores, Result produce una excepcin cuando el cdigo intenta tener acceso al
mismo. Puede evitar este problema mediante la opcin OnlyOnRanToCompletion, como se muestra
en el siguiente ejemplo.
Dim aTask = Task(Of Integer).Factory.StartNew(Function()
Return 54
End Function)
Dim bTask = aTask.ContinueWith(Sub(antecedent)
Console.WriteLine("continuation {0}", antecedent.Result)
End Sub, TaskContinuationOptions.OnlyOnRanToCompletion)
Si desea que la continuacin se ejecute aunque el antecedente no se ejecute completamente, debe
usar medidas de proteccin contra la excepcin. Un posible enfoque es probar el estado del
antecedente e intentar tener acceso a Result solamente si el estado no es Faulted o Canceled.
Tambin puede examinar la propiedad Exception del antecedente.
Cancelar una continuacin
Una continuacin pasa al estado Canceled en estos escenarios:
Cuando produce una excepcin OperationCanceledException en respuesta a una solicitud de
cancelacin. Al igual que sucede con cualquier tarea, si la excepcin contiene el mismo token
que se pas a la continuacin, se trata como una confirmacin de cancelacin cooperativa.
Cuando la continuacin se pas System.Threading.CancellationToken como un argumento y
la propiedad IsCancellationRequested del smbolo es true (True) antes de la continuacin se
ejecuta. En este caso, la continuacin no se inicia y pasa directamente al estado Canceled.
Cuando la continuacin nunca se ejecuta porque no se cumple la condicin establecida en
TaskContinuationOptions. Por ejemplo, si una tarea entra en estado Faulted, su continuacin,
creada con la opcin NotOnFaulted, pasar al estado Canceled y no se ejecutar.
Para que una continuacin no se ejecute si su antecedente se cancela, especifique la opcin
NotOnCanceled al crear la continuacin.
Si una tarea y su continuacin representan dos partes de la misma operacin lgica, puede pasar el
mismo token de cancelacin a ambas tareas, como se muestra en el siguiente ejemplo.
Dim someCondition As Boolean = True
Dim cts As New CancellationTokenSource
Dim task1 = New Task(Sub()
Dim ct As CancellationToken = cts.Token
While someCondition = True
ct.ThrowIfCancellationRequested()
' Do the work here...
End While
End Sub,
cts.Token
)
Dim task2 = task1.ContinueWith(Sub(antecedent)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 26 de 107
Dim ct As CancellationToken = cts.Token
While someCondition = True
ct.ThrowIfCancellationRequested()
' Do the work here
End While
End Sub,
cts.Token)
task1.Start()
' Antecedent and/or continuation will
' respond to this request, depending on when it is made.
cts.Cancel()
Si el antecedente no se cancela, todava se puede usar el token para cancelar la continuacin. Si el
antecedente se cancela, no se inicia la continuacin.
Despus de que una continuacin 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.
Continuaciones y tareas secundarias
Una continuacin no se ejecuta hasta que se completan su antecedente y todas las tareas
secundarias asociadas. La continuacin no espera a que se completen las tareas secundarias
desasociadas. El estado final de la tarea antecedente depende del estado final de cualquier tarea
secundaria asociada. El estado de las tareas secundarias desasociadas no afecta a la tarea primaria.
Controlar las excepciones que producen las continuaciones
Una relacin entre un antecedente y una continuacin no es una relacin primario-secundario. Las
excepciones producidas por las continuaciones no se propagan al antecedente. Por consiguiente, las
excepciones que producen las continuaciones se deben controlar de igual modo que en cualquier
otra tarea, como se indica a continuacin.
1. Use el mtodo Wait, WaitAny o WaitAll, o su homlogo genrico, para esperar en la
continuacin. Puede esperar a un antecedente y sus continuaciones en la misma instruccin
try (Try en Visual Basic), como se muestra en el siguiente ejemplo.
Dim task1 = Task(Of Integer).Factory.StartNew(Function()
Return 54
End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
Console.WriteLine("continuation {0}", antecedent.Result)
Throw New InvalidOperationException()
End Sub)
Try
task1.Wait()
continuation.Wait()
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine(ex.Message)
Next
End Try
Console.WriteLine("Exception handled. Let's move on.")
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 27 de 107
1. Use una segunda continuacin para observar la propiedad Exception de la primera
continuacin.
2. Si la continuacin es una tarea secundaria creada mediante la opcin AttachedToParent, la
tarea primaria propagar sus excepciones al subproceso que realiza la llamada, como sucede
con cualquier otro elemento secundario asociado.
1.2.2. Tareas anidadas y tareas secundarias
Una tarea anidada no es ms que una instancia de Task que se crea en el delegado de usuario de
otra tarea. Una tarea secundaria es una tarea anidada que se crea con la opcin AttachedToParent.
Una tarea puede crear cualquier nmero de tareas secundarias y anidadas, con la nica limitacin de
los recursos del sistema. En el ejemplo siguiente se muestra una tarea primaria que crea una tarea
anidada simple.
Shared Sub SimpleNestedTask()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(500000)
Console.WriteLine("Nested task completing.")
End Sub)
End Sub)
parent.Wait()
Console.WriteLine("Outer task has completed.")
End Sub
' Sample output:
' Outer task executing.
' Nested task starting.
' Outer task has completed.
' Nested task completing.
Tareas secundarias asociadas frente a tareas anidadas desasociadas
El aspecto ms 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 estn estrechamente sincronizadas con el la tarea primaria. Si se modifica la
instruccin de creacin de la tarea para usar la opcin AttachedToParent, como se muestra en el
siguiente ejemplo,
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
se generar el siguiente resultado.
' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.
Puede usar tareas secundarias asociadas para crear grficos de operaciones asincrnicas con una
estrecha sincronizacin. Sin embargo, en la mayora de los escenarios, recomendamos usar tareas
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 28 de 107
anidadas porque las relaciones con otras tareas son menos complejas. Esta es la razn por la que las
tareas que se crean dentro de otras tareas estn anidadas de forma predeterminada y es necesario
especificar explcitamente la opcin AttachedToParent para crear una tarea secundaria.
En la tabla siguiente se muestran las diferencias bsicas entre los dos tipos de tareas secundarias.
Categora
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
En escenarios desasociados en los que la tarea anidada es un objetoTask(Of 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.
Shared Sub WaitForSimpleNestedTask()
Dim parent = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Outer task executing.")
Dim child = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Nested task completing.")
Return 42
End Function)
Return child.Result
End Function)
Console.WriteLine("Outer has returned {0}", parent.Result)
End Sub
'Sample output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
Excepciones en tareas anidadas y secundarias
Si una tarea anidada produce una excepcin, debe observarse o controlarse directamente en la tarea
exterior como si se tratara de una tarea no anidada. Si un tiros del elemento secundario adjuntos una
excepcin, la excepcin se propaga automticamente a la tarea primaria y atrs al subproceso que
espera o intenta tener 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 punto: la llamada a Wait
del subproceso que realiza la llamada.
Cancelacin y tareas secundarias
No conviene olvidar que la cancelacin de tareas es cooperativa. Por tanto, para ser "cancelable",
cada tarea secundaria asociada o desasociada debe supervisar el estado del token de cancelacin. Si
desea cancelar un elemento primario y todos sus elementos secundarios utilizando una sola solicitud
de cancelacin, debe pasar el mismo token como argumento a todas las tareas y proporcionar en
cada tarea la lgica de respuesta a la solicitud.
Cuando la tarea primaria se cancela
Si una tarea primaria se cancela antes de que se inicie una tarea secundaria, como es lgico, la tarea
secundaria (anidada) nunca se cancelar. Si una tarea primaria se cancela despus de que se ha
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 29 de 107
iniciado una tarea secundaria o anidada, la tarea anidada (secundaria) se ejecutar hasta completarse
a menos que tenga su propia lgica de cancelacin.
Cuando una tarea anidada se cancela
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 excepcin, pues la excepcin se
trata cono una cancelacin de cooperacin benigna. Este comportamiento es igual que el de
cualquier tarea de nivel superior.
Cuando una tarea secundaria se cancela
Cuando una tarea secundaria asociada se cancela usando el mismo token que se pas a la tarea, se
propaga una excepcin TaskCanceledException al subproceso de unin dentro de
AggregateException. Es muy importante esperar a la tarea primaria para poder controlar todas las
excepciones benignas adems de todos las excepciones de error que se propagan de manera
ascendente a travs de un grfico de tareas secundarias asociadas.
1.2.3. Cancelacin de tareas
Las clases System.Threading.Tasks.Task(Of TResult) y System.Threading.Tasks.Task admiten la
cancelacin a travs del uso de tokens de cancelacin, que son una novedad de .NET Framework 4.
En las clases Task, la cancelacin implica la cooperacin entre el delegado de usuario, que representa
una operacin cancelable y el cdigo que solicitaron la cancelacin. UNA cancelacin correcta
implica el cdigo para solicitar que llama al mtodo CancellationTokenSource.Cancely el delegado de
usuario que finaliza la operacin de una manera oportuna. Puede finalizar la operacin a travs de
una de estas opciones:
Devolver simplemente un valor del delegado. En muchos escenarios esto es suficiente; sin
embargo, una instancia de la tarea que est cancelada de esta manera las transiciones a
RanToCompletion dice, no al estado Cancelado.
Producir una excepcin OperationCanceledException y pasarle el token en el que se solicit
la cancelacin. En este caso, se prefiere usar el mtodo ThrowIfCancellationRequested. Una
tarea cancelada de esta manera cambia al estado Canceled, que sirve al cdigo que realiza la
llamada para comprobar que la tarea respondi a su solicitud de cancelacin.
En el siguiente ejemplo se muestra el modelo bsico para la opcin de cancelacin de tareas que
produce la excepcin. Observe que el token se pasa al delegado de usuario y a la propia instancia de
la tarea.
Imports System.Threading
Imports System.Threading.Tasks
Module Test
Sub Main()
Dim tokenSource2 As New CancellationTokenSource()
Dim ct As CancellationToken = tokenSource2.Token
Dim t2 = Task.Factory.StartNew(Sub()
' Were we already canceled?
ct.ThrowIfCancellationRequested()
Dim moreToDo As Boolean = True
While moreToDo = True
' Poll on this property if you have to do other cleanup before throwing.
If ct.IsCancellationRequested Then
' Clean up here, then...
ct.ThrowIfCancellationRequested()
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 30 de 107
End If
End While
End Sub _
, tokenSource2.Token) ' Pass same token to StartNew.
' Cancel the task.
tokenSource2.Cancel()
' Just continue on this thread, or Wait/WaitAll with try-catch:
Try
t2.Wait()
Catch e As AggregateException
For Each item In e.InnerExceptions
Console.WriteLine(e.Message & " " & item.Message)
Next
End Try
Console.ReadKey()
End Sub
End Module
Cuando una instancia de tarea observa una excepcin OperationCanceledException iniciada desde el
cdigo de usuario, compara el token de la excepcin con su token asociado (el que se pas a la API
que cre la tarea). Si son los mismos y la propiedad IsCancellationRequested del smbolo devuelve
verdadero, la tarea interpreta esto como confirmar cancelacin y transiciones al estado Cancelado. Si
no utiliza mtodo WaitAll sino Wait para esperar por la tarea, a continuacin, la tarea apenas
establece su estado en Canceled.
Si espera en una tarea que cambia al estado Canceled, se crea y se inicia una excepcin
TaskCanceledException (encapsulada en AggregateException). Observe que esta excepcin indica la
cancelacin correcta en lugar de una situacin 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 excepcin no
coincide con el token de la tarea, OperationCanceledException se trata como una excepcin normal,
por lo que la tarea cambia al estado Faulted. Observe tambin que la presencia de otras excepciones
tambin har que la tarea pase al estado Faulted. Puede obtener el estado de la tarea completada en
la propiedad Status.
Es posible que una tarea contine procesando algunos elementos una vez solicitada la cancelacin.

1.2.4. Control de excepciones (Task Parallel Library)
Las excepciones no controladas que se inician mediante el cdigo de usuario que se ejecuta dentro
de una tarea se propagan de nuevo al subproceso de unin, excepto en determinados escenarios
que se describen posteriormente en este tema. Se propagan Excepciones al utilizar uno de los
mtodos Task(Of TResult).Wait o esttica o instancia Task.Wait, y los administra agregando la llamada
en una instruccin de try-catch. Si una tarea es la tarea primaria de unas tareas secundarias asociadas
o si se esperan varias tareas, pueden producirse varias excepciones. Para propagar todas las
excepciones de nuevo al subproceso que realiza la llamada, la infraestructura de la tarea las
encapsula en una instancia de AggregateException. AggregateException tiene una propiedad
InnerExceptions que se puede enumerar para examinar todas las excepciones originales que se
generaron y controlar (o no) cada una de ellas de forma individual. Aunque solo se inicie una nica
excepcin, se encapsular en un objeto AggregateException.
Dim task1 = Task.Factory.StartNew(Sub()
Throw New MyCustomException("I'm bad, but not too bad!")
End Sub)
Try
task1.Wait()
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 31 de 107
Catch ae As AggregateException
' 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.
For Each ex In ae.InnerExceptions
If TypeOf (ex) Is MyCustomException Then
Console.WriteLine(ex.Message)
Else
Throw
End If
Next
End Try
Para evitar una excepcin no controlada, basta con detectar el objeto AggregateException y omitir
las excepciones internas. Sin embargo, esta operacin no resulta recomendable porque es igual que
detectar el tipo Exception base en escenarios no paralelos. Si desea detectar una excepcin sin
realizar acciones concretas que la resuelvan, puede dejar al programa en un estado indeterminado.
Si no espera que ninguna tarea propague la excepcin ni tiene acceso a su propiedad Exception, la
excepcin 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 unin, es posible que una
tarea contine procesando algunos elementos despus de que se haya producido la excepcin.
Tareas secundarias asociadas y objetos AggregateException anidados
Si una tarea tiene una tarea secundaria adjunta que inicia una excepcin, esa excepcin se encapsula
en un objeto AggregateException antes de que se propague a la tarea primaria, que encapsula esa
excepcin en su propio objeto AggregateException antes de propagarla de nuevo al subproceso que
realiza la llamada. En casos como ste, la AggregateException.InnerExceptions propiedad de
AggregateException que se detecta en Task.Wait u Task(Of TResult).Wait u WaitAny o el mtodo
WaitAll contiene uno o ms AggregateException crea instancias, no las excepciones originales que
produjeron el error. Para evitar tener que iterar sobre AggregateExceptions anidado, puede utilizar el
mtodo Flatten para quitar todo el AggregateExceptions anidado, para que la AggregateException
InnerExceptions propiedad contenga las excepciones originales. En el siguiente ejemplo, las
instancias AggregateException anidadas se quitan informacin de estructura jerrquica y administran
en slo un bucle.
' task1 will throw an AE inside an AE inside an AE
Dim task1 = Task.Factory.StartNew(Sub()
Dim child1 = Task.Factory.StartNew(Sub()
Dim child2 = Task.Factory.StartNew(Sub()
Throw New MyCustomException("Attached child2 faulted.")
End Sub,
TaskCreationOptions.AttachedToParent)
End Sub,
TaskCreationOptions.AttachedToParent)
' Uncomment this line to see the exception rethrown. throw new MyCustomException("Attached child1 faulted.")
End Sub)
Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf (ex) Is MyCustomException Then
Console.WriteLine(ex.Message)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 32 de 107
Else
Throw
End If
Next
'or like this:
' ae.Flatten().Handle(Function(e)
' Return TypeOf (e) Is MyCustomException
' End Function)
End Try
Excepciones de tareas secundarias desasociadas
De forma predeterminada, las tareas secundarias estn desasociadas cuando se crean. Las
excepciones producidas por tareas desasociadas deben controlarse o reiniciarse en la tarea primaria
inmediata; no se propagan de nuevo al subproceso que realiza la llamada del mismo modo que las
tareas secundarias asociadas. La tarea primaria superior puede reiniciar manualmente una excepcin
de una tarea desasociada para encapsularla en un objeto AggregateException y propagarla de nuevo
al subproceso de unin.
Dim task1 = Task.Factory.StartNew(Sub()
Dim nestedTask1 = Task.Factory.StartNew(Sub()
Throw New MyCustomException("Nested task faulted.")
End Sub)
' Here the exception will be escalated back to joining thread.
' We could use try/catch here to prevent that.
nestedTask1.Wait()
End Sub)
Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf (ex) Is MyCustomException Then
' Recover from the exception. Here we just
' print the message for demonstration purposes.
Console.WriteLine(ex.Message)
End If
Next
End Try
Aunque se use una tarea de continuacin para observar una excepcin en una tarea secundaria, la
tarea primaria debe seguir observando la excepcin.
Excepciones que indican la cancelacin cooperativa
Cuando el cdigo de usuario de una tarea responde a una solicitud de cancelacin, el procedimiento
correcto es producir una excepcin OperationCanceledException que se pasa en el token de
cancelacin con el que se comunic la solicitud. Antes de intentar propagar la excepcin, la instancia
de la tarea compara el token de la excepcin con el que recibi durante su creacin. Si son iguales, la
tarea propaga una excepcin TaskCanceledException encapsulada en un elemento
AggregateException y puede verse cuando se examinan las excepciones internas. Sin embargo, si el
subproceso de unin no est esperando la tarea, no se propagar esta excepcin concreta.
Dim someCondition As Boolean = True
Dim tokenSource = New CancellationTokenSource()
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 33 de 107
Dim token = tokenSource.Token
Dim task1 = Task.Factory.StartNew(Sub()
Dim ct As CancellationToken = token
While someCondition = True
' Do some work...
Thread.SpinWait(500000)
ct.ThrowIfCancellationRequested()
End While
End Sub,
token)
Usar el mtodo Handle para filtrar excepciones internas
El mtodo Handle puede usarse para filtrar excepciones que pueden tratarse como "controladas" sin
necesidad de usar ninguna otra lgica. En el delegado de usuario que se proporciona a Handle, se
puede examinar el tipo de excepcin, su propiedad Message o cualquier otra informacin sobre esta
excepcin que permita determinar si es benigna. Las excepciones en las que el delegado devuelve
false se reinician inmediatamente en una nueva instancia de AggregateException despus de que
Handle devuelve un valor.
En el siguiente fragmento de cdigo se usa un bucle foreach sobre las excepciones internas.
For Each ex In ae.InnerExceptions
If TypeOf (ex) Is MyCustomException Then
Console.WriteLine(ex.Message)
Else
Throw
End If
Next
En el siguiente fragmento de cdigo se muestra el uso del mtodo Handle con la misma funcin.
ae.Handle(Function(ex)
Return TypeOf (ex) Is MyCustomException
End Function)
Observar excepciones mediante la propiedad Task.Exception
Si una tarea completa en el estado Faulted, su propiedad Exception se puede examinar para detectar
qu excepcin concreta produjo el error. Un mecanismo adecuado para observar la propiedad
Exception es usar una continuacin que se ejecute solo si se produce un error en la tarea anterior, tal
y como se muestra en el siguiente ejemplo.
Dim task1 = Task.Factory.StartNew(Sub()
Throw New MyCustomException("task1 faulted.")
End Sub).ContinueWith(Sub(t)
Console.WriteLine("I have observed a {0}",
t.Exception.InnerException.GetType().Name)
End Sub, TaskContinuationOptions.OnlyOnFaulted)
En una aplicacin real, el delegado de continuacin podra registrar informacin detallada sobre la
excepcin y posiblemente generar nuevas tareas para recuperarse de la excepcin.
Evento UnobservedTaskException
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 34 de 107
En algunos escenarios (por ejemplo, cuando se hospedan complementos que no son de confianza),
es posible que se produzcan numerosas excepciones benignas y que resulte demasiado difcil
observarlas todas manualmente. En estos casos, se puede proceder a controlar el evento
TaskScheduler.UnobservedTaskException. La instancia de
System.Threading.Tasks.UnobservedTaskExceptionEventArgs que se pasa al controlador se puede
utilizar para evitar que la excepcin no observada se propague de nuevo al subproceso de unin.
1.2.5. Cmo: Usar Parallel.Invoke para ejecutar operaciones paralelas
En este ejemplo se muestra cmo paralelizar las operaciones utilizando ParallelInvoke en la biblioteca
TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas). En un origen de
datos compartido se realizan tres operaciones. Dado que ninguna de ellas modifica el origen, se
pueden ejecutar en paralelo de manera sencilla.
Ejemplo
' How to: Use Parallel.Invoke to Execute Parallel Operations
Option Explicit On
Option Strict On
Imports System.Threading.Tasks
Imports System.Net
Module ParallelTasks
Sub Main()
' Retrieve Darwin's "Origin of the Species" from Gutenberg.org.
Dim words As String() = CreateWordArray("http://www.gutenberg.org/files/2009/2009.txt")
'#Region "ParallelTasks"
' Perform three tasks in parallel on the source array
Parallel.Invoke(Sub()
Console.WriteLine("Begin first task...")
GetLongestWord(words)
' close first Action
End Sub,
Sub()
Console.WriteLine("Begin second task...")
GetMostCommonWords(words)
'close second Action
End Sub,
Sub()
Console.WriteLine("Begin third task...")
GetCountForWord(words, "species")
'close third Action
End Sub)
'close parallel.invoke
Console.WriteLine("Returned from Parallel.Invoke")
'#End Region
Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub
#Region "HelperMethods"
Sub GetCountForWord(ByVal words As String(), ByVal term As String)
Dim findWord = From word In words _
Where word.ToUpper().Contains(term.ToUpper()) _
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 35 de 107
Select word
Console.WriteLine("Task 3 -- The word ""{0}"" occurs {1} times.", term, findWord.Count())
End Sub
Sub GetMostCommonWords(ByVal words As String())
Dim frequencyOrder = From word In words _
Where word.Length > 6 _
Group By word
Into wordGroup = Group, Count()
Order By wordGroup.Count() Descending _
Select wordGroup
Dim commonWords = From grp In frequencyOrder
Select grp
Take (10)
Dim s As String
s = "Task 2 -- The most common words are:" & vbCrLf
For Each v In commonWords
s = s & v(0) & vbCrLf
Next
Console.WriteLine(s)
End Sub
Function GetLongestWord(ByVal words As String()) As String
Dim longestWord = (From w In words _
Order By w.Length Descending _
Select w).First()
Console.WriteLine("Task 1 -- The longest word is {0}", longestWord)
Return longestWord
End Function
' An http request performed synchronously for simplicity.
Function CreateWordArray(ByVal uri As String) As String()
Console.WriteLine("Retrieving from {0}", uri)
' Download a web page the easy way.
Dim s As String = New WebClient().DownloadString(uri)
' Separate string into an array of words, removing some common punctuation.
Return s.Split(New Char() {" "c, ControlChars.Lf, ","c, "."c, ";"c, ":"c, _
"-"c, "_"c, "/"c}, StringSplitOptions.RemoveEmptyEntries)
End Function
#End Region
' Output (May vary on each execution):
' Retrieving from http://www.gutenberg.org/dirs/etext99/otoos610.txt
' Response stream received.
' Begin first task...
' Begin second task...
' Task 2 -- The most common words are:
' species
' selection
' varieties
' natural
' animals
' between
' different
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 36 de 107
' distinct
' several
' conditions
' Begin third task...
' Task 1 -- The longest word is characteristically
' Task 3 -- The word "species" occurs 1927 times.
' Returned from Parallel.Invoke
' Press any key to exit
End Module
Observe que con Invoke, simplemente expresa qu acciones desea que se ejecuten simultneamente;
el runtime controla todos los detalles de programacin de subprocesos, incluido el escalado
automtico al nmero de ncleos del equipo host.
En este ejemplo se paralelizan las operaciones, no los datos. Como enfoque alternativo, puede
paralelizar los consultas LINQ mediante PLINQ y ejecutar las consultas de forma secuencial. Tambin
puede paralelizar los datos con PLINQ. Otra opcin consiste en paralelizar las consultas y las tareas.
Aunque la sobrecarga resultante podra degradar el rendimiento en equipos host con relativamente
pocos procesadores, se ajustara mucho mejor en equipos con muchos procesadores.

1.2.6. Cmo: Devolver un valor de una tarea
En este ejemplo se muestra cmo se usa el tipo System.Threading.Tasks.Task(Of TResult) para
devolver un valor de la propiedad Result.
Ejemplo
' How to: Return a Value from a Task
Imports System.Threading.Tasks
Module Module1
Sub Main()
ReturnAValue()
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Sub ReturnAValue()
' Return a value type with a lambda expression
Dim task1 = Task(Of Integer).Factory.StartNew(Function() 1)
Dim i = task1.Result
' Return a named reference type with a multi-line statement lambda.
Dim task2 As Task(Of Test) = Task.Factory.StartNew(Function()
Dim s As String = ".NET"
Dim d As Integer = 4.0
Return New Test With {.Name = s, .Number = d}
End Function)
Dim myTest As Test = task2.Result
Console.WriteLine(myTest.Name & ":" & myTest.Number)
' Return an array produced by a PLINQ query
Dim task3 As Task(Of String())= Task(Of String()).Factory.StartNew(Function()
Dim path = "C:\Users\Public\Pictures\Sample Pictures\"
Dim files = System.IO.Directory.GetFiles(path)
Dim result = (From file In files.AsParallel()
Let info = New System.IO.FileInfo(file)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 37 de 107
Where info.Extension = ".jpg"
Select file).ToArray()
Return result
End Function)
For Each name As String In task3.Result
Console.WriteLine(name)
Next
End Sub
Class Test
Public Name As String
Public Number As Double
End Class
End Module
La propiedad Result bloquea el subproceso que realiza la llamada hasta que la tarea finaliza.

1.2.7. Cmo: Esperar a que una o varias tareas se completen
En este ejemplo se muestra cmo utilizar el mtodo Wait o su equivalente en la clase Task(Of
TResult), para esperar en una tarea nica. Tambin se muestra cmo utilizar los mtodos WaitAny y
WaitAll estticos para esperar en varias tareas.
Ejemplo
' How to: Wait on One or More Tasks to Complete
Imports System.Threading
Imports System.Threading.Tasks
Module WaitOnTasks
Dim rand As New Random()
Sub Main()
' Wait on a single task with no timeout specified.
Dim taskA = Task.Factory.StartNew(Sub() DoSomeWork(10000000))
taskA.Wait()
Console.WriteLine("taskA has completed.")
' Wait on a single task with a timeout specified.
Dim taskB = Task.Factory.StartNew(Sub() DoSomeWork(10000000))
taskB.Wait(100) 'Wait for 100 ms.
If (taskB.IsCompleted) Then
Console.WriteLine("taskB has completed.")
Else
Console.WriteLine("Timed out before task2 completed.")
End If
' Wait for all tasks to complete.
Dim myTasks(9) As Task
For i As Integer = 0 To myTasks.Length - 1
myTasks(i) = Task.Factory.StartNew(Sub() DoSomeWork(10000000))
Next
Task.WaitAll(myTasks)
' Wait for first task to complete.
Dim tasks2(2) As Task(Of Double)
' Try three different approaches to the problem. Take the first one.
tasks2(0) = Task(Of Double).Factory.StartNew(Function() TrySolution1())
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 38 de 107
tasks2(1) = Task(Of Double).Factory.StartNew(Function() TrySolution2())
tasks2(2) = Task(Of Double).Factory.StartNew(Function() TrySolution3())
Dim index As Integer = Task.WaitAny(tasks2)
Dim d As Double = tasks2(index).Result
Console.WriteLine("task(0) completed first with result of {1}.", index, d)
Console.ReadKey()
End Sub
' Dummy Functions to Simulate Work
Function DoSomeWork(ByVal val As Integer)
' Pretend to do something.
Thread.SpinWait(val)
End Function
Function TrySolution1()
Dim i As Integer = rand.Next(1000000)
' Simulate work by spinning
Thread.SpinWait(i)
Return i
End Function
Function TrySolution2()
Dim i As Integer = rand.Next(1000000)
' Simulate work by spinning
Thread.SpinWait(i)
Return i
End Function
Function TrySolution3()
Dim i As Integer = rand.Next(1000000)
' Simulate work by spinning
Thread.SpinWait(i)
Thread.SpinWait(1000000)
Return i
End Function
End Module
Por razones de simplicidad, estos ejemplos no muestran el cdigo de control de excepciones ni el
cdigo de cancelacin. En la mayora de los casos, debe incluir un mtodo Wait en un bloque try-
catch, porque la espera es el mecanismo por el que el cdigo de programa controla las excepciones
que se inician en cualquiera de las tareas. Si la tarea se puede cancelar, debe comprobar las
propiedades IsCanceled o IsCancellationRequested antes de intentar utilizar la tarea o su propiedad
Result.

1.2.8. Cmo: Cancelar una tarea y sus elementos secundarios
En estos ejemplos se muestra cmo realizar las tareas siguientes:
1. Crear e iniciar una tarea cancelable.
2. Pasar un token de cancelacin a un delegado de usuario y, opcionalmente, a la instancia de
la tarea.
3. Observar y responder a la solicitud de cancelacin en el delegado de usuario.
4. Opcionalmente, observar en el subproceso que realiza la llamada que la tarea se cancel.
El subproceso que realiza la llamada no finaliza la tarea forzosamente, sino que solo seala que se
solicita la cancelacin. Si la tarea ya se est ejecutando, es el delegado de usuario el que debe
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 39 de 107
observar la solicitud y responder segn corresponda. Si la cancelacin se solicita antes de ejecutarse
la tarea, el delegado de usuario nunca se ejecuta y el objeto de tarea pasa al estado Cancelado.
Ejemplo
En este ejemplo se muestra cmo finalizar un objeto Task y sus elementos secundarios en respuesta
a una solicitud de cancelacin. Tambin se muestra que, cuando un delegado de usuario finaliza con
una excepcin OperationCanceledException, el subproceso que realiza la llamada puede usar
opcionalmente el mtodo Wait o el mtodo WaitAll para esperar a que las tareas finalicen. En este
caso, el delegado debe usar un bloque try-catch para controlar las excepciones en el subproceso que
realiza la llamada.
' How to: Cancel a Task and Its Children
Imports System.Threading
Imports System.Threading.Tasks
Module CancelATask
Sub Main()
Console.WriteLine("Press any key to start. Press 'c' to cancel.")
Console.ReadKey()
Dim tokenSource As New CancellationTokenSource()
Dim token As CancellationToken = tokenSource.Token
' Store references to the tasks so that we can wait on them and observe their status after cancellation.
Dim tasks(10) As Task
' Request cancellation of a single task when the token source is canceled.
' Pass the token to the user delegate, and also to the task so it can handle the exception correctly.
tasks(0) = Task.Factory.StartNew(Sub() 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(Sub()
' Create some cancelable child tasks.
For i As Integer = 2 To 10
' For each child task, pass the same token to each user delegate and to StartNew.
tasks(i) = Task.Factory.StartNew(Sub(iteration) DoSomeWork(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)
Next
End Sub _
, token)
' Give the tasks a second to start.
Thread.Sleep(1000)
' Request cancellation from the UI thread.
If Console.ReadKey().KeyChar = "c"c Then
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.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 40 de 107
End If
Try
Task.WaitAll(tasks)
Catch e As AggregateException
' For demonstration purposes, show the OCE message.
For Each v In e.InnerExceptions
Console.WriteLine("msg: " + v.Message)
Next
End Try
' Prove that the tasks are now all in a canceled state.
For i As Integer = 0 To tasks.Length
Console.WriteLine("task(0) status is now 1", i, tasks(i).Status)
Next
' Keep the console window open while the task completes its output.
Console.ReadLine()
End Sub
Sub DoSomeWork(ByVal taskNum As Integer, ByVal ct As CancellationToken)
' Was cancellation already requested?
If ct.IsCancellationRequested = True Then
Console.WriteLine("We were cancelled before we got started.")
Console.WriteLine("Press Enter to quit.")
ct.ThrowIfCancellationRequested()
End If
Dim maxIterations As Integer = 1000
' NOTE!!! An "OperationCanceledException was unhandled by user code" error will be raised here if "Just My
Code"
' is enabled on your computer. On Express editions JMC is enabled and cannot be disabled. The exception is
benign.
' Just press F5 to continue executing your code.
For i As Integer = 0 To maxIterations
' Do a bit of work. Not too much.
Dim sw As New SpinWait()
For j As Integer = 0 To 3000
sw.SpinOnce()
Next
Console.WriteLine("...0 ", taskNum)
If ct.IsCancellationRequested Then
Console.WriteLine("bye from 0.", taskNum)
Console.WriteLine("\nPress Enter to quit.")
ct.ThrowIfCancellationRequested()
End If
Next
End Sub
End Module
La clase System.Threading.Tasks.Task est totalmente integrada con el modelo de cancelacin
basado en los tipos System.Threading.CancellationToken y
System.Threading.CancellationTokenSource.

1.2.9. Cmo: Controlar excepciones iniciadas por tareas
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 41 de 107
En los siguientes ejemplos, se muestra cmo controlar las excepciones producidas en una o varias
tareas.
Ejemplo
En este primer ejemplo, se detecta la excepcin System.AggregateException y, a continuacin, se
examina su propiedad AggregateExceptionInnerExceptions para determinar si algunas de las
excepciones se pueden controlar mediante el cdigo del programa.
' How to: Handle Exceptions Thrown by Tasks
Imports System.Threading.Tasks
Module TaskExceptions
Function GetAllFiles(ByVal str As String) As String()
' Should throw an AccessDenied exception on Vista or later. If you see an "Exception was unhandled
' by user code" error, this is because "Just My Code" is enabled. Press F5 to continue execution or
' disable Just My Code.
Return System.IO.Directory.GetFiles(str, "*.txt", System.IO.SearchOption.AllDirectories)
End Function
Sub Main()
HandleExceptions()
' RethrowAllExceptions()
Console.WriteLine("Press any key.")
Console.ReadKey()
End Sub
Sub HandleExceptions()
' Assume this is a user-entered String.
Dim path = "C:\"
' Use this line to throw UnauthorizedAccessException, which we handle.
Dim task1 = Task(Of String()).Factory.StartNew(Function() GetAllFiles(path))
' Use this line to throw an exception that is not handled.
' Task task1 = Task.Factory.StartNew(Sub () throw new IndexOutOfRangeException() )
Try
task1.Wait()
Catch ae As AggregateException
ae.Handle(Function(x)
If TypeOf (x) Is UnauthorizedAccessException Then ' 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
Else
Return False ' Let anything else stop the application.
End If
End Function)
End Try
Console.WriteLine("task1 has completed.")
End Sub
Function GetValidExtensions(ByVal path As String) As String()
If path = "C:\" Then
Throw New ArgumentException("The system root is not a valid path.")
End If
Dim result(10) As String
Return result
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 42 de 107
End Function
Sub RethrowAllExceptions()
' Assume this is a user-entered String.
Dim path = "C:\"
Dim myTasks(2) As Task(Of String())
myTasks(0) = Task(Of String()).Factory.StartNew(Function() GetAllFiles(path))
myTasks(1) = Task(Of String()).Factory.StartNew(Function() GetValidExtensions(path))
myTasks(2) = Task(Of String()).Factory.StartNew(Function()
Dim s(10) As String
Return s
End Function)
Try
Task.WaitAll(myTasks)
Catch ae As AggregateException
Throw ae.Flatten()
End Try
Console.WriteLine("task1 has completed.")
End Sub
End Module
En este ejemplo, se detecta la excepcin System.AggregateException, pero no se intenta controlar
ninguna de sus excepciones internas. En su lugar, se utiliza el mtodo Flatten para extraer las
excepciones internas de todas las instancias anidadas de AggregateException y volver a iniciar una
sola excepcin AggregateException que contiene directamente todas las excepciones internas no
controladas. Al reducir la excepcin, resulta ms fcil controlarla mediante cdigo de cliente.
Imports System.Threading.Tasks
Module TaskExceptions2
Sub Main()
RethrowAllExceptions()
End Sub
Function GetAllFiles(ByVal str As String) As String()
' Should throw an AccessDenied exception on Vista or later. If you see an "Exception was unhandled
' by user code" error, this is because "Just My Code" is enabled. Press F5 to continue execution or
' disable Just My Code.
Return System.IO.Directory.GetFiles(str, "*.txt", System.IO.SearchOption.AllDirectories)
End Function
Function GetValidExtensions(ByVal path As String) As String()
If path = "C:\" Then
Throw New ArgumentException("The system root is not a valid path.")
End If
Dim result(10) As String
Return result
End Function
Sub RethrowAllExceptions()
' Assume this is a user-entered String.
Dim path = "C:\"
Dim myTasks(2) As Task(Of String())
myTasks(0) = Task(Of String()).Factory.StartNew(Function() GetAllFiles(path))
myTasks(1) = Task(Of String()).Factory.StartNew(Function() GetValidExtensions(path))
myTasks(2) = Task(Of String()).Factory.StartNew(Function()
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 43 de 107
Dim s(10) As String
Return s
End Function)
Try
Task.WaitAll(myTasks)
Catch ae As AggregateException
Throw ae.Flatten()
End Try
Console.WriteLine("task1 has completed.")
End Sub
End Module
Para ejecutar este ejemplo, pegue el cdigo en el ejemplo anterior y llame a RethrowAllExceptions
desde el mtodo Main.

1.2.10. Cmo: Encadenar varias tareas con continuaciones
In the Task Parallel Library, a task whose ContinueWith method is invoked is called the antecedent
task and the task that is defined in the ContinueWith method is called the continuation. En este
ejemplo se muestra cmo utilizar los mtodos ContinueWith y ContinueWith de las clases Task(Of
TResult) y Task para especificar una tarea que se inicia despus de que sus finales de la tarea
antecedentes.
Tambin muestra cmo especificar una continuacin que solo se ejecuta si la tarea antecedente se
cancela.
En estos ejemplos se muestra cmo continuar desde una tarea nica. Tambin puede crear una
continuacin que se ejecute despus de que alguno o todos los grupos de tareas se completen o se
cancelen.
Ejemplo
En el mtodo DoSimpleContinuation, se muestra la sintaxis bsica de ContinueWith. Observe que la
tarea antecedente se proporciona como el parmetro de entrada a la expresin lambda en el mtodo
ContinueWith. Esto le permite evaluar el estado de la tarea antecedente antes de realizar cualquier
trabajo en la continuacin. Utilice esta sobrecarga simple de ContinueWith cuando no tenga que
pasar un estado de una tarea a otra.
En el mtodo DoSimpleContinuationWithState, se muestra cmo utilizar ContinueWith para pasar el
resultado de la tarea antecedente a la tarea de continuacin.
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
Module ContinueWith
Sub Main()
DoSimpleContinuation()
Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub
Sub DoSimpleContinuation()
Dim path As String = "C:\users\public\TPLTestFolder\"
Try
Dim firstTask = New Task(Sub() CopyDataIntoTempFolder(path))
Dim secondTask = firstTask.ContinueWith(Sub(t) CreateSummaryFile(path))
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 44 de 107
firstTask.Start()
Catch e As AggregateException
Console.WriteLine(e.Message)
End Try
End Sub
' A toy function to simulate a workload
Sub CopyDataIntoTempFolder(ByVal path__1 As String)
System.IO.Directory.CreateDirectory(path__1)
Dim rand As New Random()
For x As Integer = 0 To 49
Dim bytes As Byte() = New Byte(999) {}
rand.NextBytes(bytes)
Dim filename As String = Path.GetRandomFileName()
Dim filepath As String = Path.Combine(path__1, filename)
System.IO.File.WriteAllBytes(filepath, bytes)
Next
End Sub
Sub CreateSummaryFile(ByVal path__1 As String)
Dim files As String() = System.IO.Directory.GetFiles(path__1)
Parallel.ForEach(files, Sub(file)
Thread.SpinWait(5000)
End Sub)
System.IO.File.WriteAllText(Path.Combine(path__1, "__SummaryFile.txt"), "did my work")
Console.WriteLine("Done with task2")
End Sub
Sub DoSimpleContinuationWithState()
Dim nums As Integer() = {19, 17, 21, 4, 13, 8, _
12, 7, 3, 5}
Dim f0 = New Task(Of Double)(Function() nums.Average())
Dim f1 = f0.ContinueWith(Function(t) GetStandardDeviation(nums, t.Result))
f0.Start()
Console.WriteLine("the standard deviation is {0}", f1)
End Sub
Function GetStandardDeviation(ByVal values As Integer(), ByVal mean As Double) As Double
Dim d As Double = 0.0R
For Each n In values
d += Math.Pow(mean - n, 2)
Next
Return Math.Sqrt(d / (values.Length - 1))
End Function
End Module
El parmetro de tipo de Task(Of TResult) determina el tipo devuelto del delegado. Ese valor devuelto
se pasa a la tarea de continuacin. Es posible encadenar un nmero arbitrario de tareas de esta
manera.

1.2.11. Cmo: Recorrer un rbol binario con tareas paralelas
En el siguiente ejemplo se muestran dos maneras de usar tareas paralelas para atravesar una
estructura de datos en rbol. La creacin del propio rbol se deja como ejercicio.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 45 de 107
Ejemplo
Imports System.Threading.Tasks
Public Class TreeWalk
Shared Sub Main()
Dim tree As Tree(Of Person) = New Tree(Of Person)()
' ...populate tree (left as an exercise)
' Define the Action to perform on each node.
Dim myAction As Action(Of Person) = New Action(Of Person)(Sub(x)
Console.WriteLine("{0} : {1} ", x.Name, x.Number)
End Sub)
' Traverse the tree with parallel tasks.
DoTree(tree, myAction)
End Sub
Public Class Person
Public Name As String
Public Number As Integer
End Class
Public Class Tree(Of T)
Public Left As Tree(Of T)
Public Right As Tree(Of T)
Public Data As T
End Class
' By using tasks explicitly.
Public Shared Sub DoTree(Of T)(ByVal myTree As Tree(Of T), ByVal a As Action(Of T))
If Not myTree Is Nothing Then
Return
End If
Dim left = Task.Factory.StartNew(Sub() DoTree(myTree.Left, a))
Dim right = Task.Factory.StartNew(Sub() DoTree(myTree.Right, a))
a(myTree.Data)
Try
Task.WaitAll(left, right)
Catch ae As AggregateException
'handle exceptions here
End Try
End Sub
' By using Parallel.Invoke
Public Shared Sub DoTree2(Of T)(ByVal myTree As Tree(Of T), ByVal myAct As Action(Of T))
If Not myTree Is Nothing Then
Return
End If
Parallel.Invoke(
Sub() DoTree2(myTree.Left, myAct),
Sub() DoTree2(myTree.Left, myAct),
Sub() myAct(myTree.Data)
)
End Sub
End Class
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 46 de 107
Los dos mtodos mostrados son equivalentes desde el punto de vista funcional. Cuando se usa el
mtodo StartNew para crear y ejecutar las tareas, estas devuelven un identificador que se puede usar
para esperar en ellas y controlar las excepciones.

1.2.12. Cmo: Desencapsular una tarea anidada
Puede devolver una tarea de un mtodo y esperar o continuar a partir de esa tarea, como se muestra
en el siguiente ejemplo:
Shared Function DoWorkAsync() As Task(Of String)
Return Task(Of String).Factory.StartNew(Function()
'...
Return "Work completed."
End Function)
End Function
Shared Sub StartTask()
Dim t As Task(Of String) = DoWorkAsync()
t.Wait()
Console.WriteLine(t.Result)
End Sub
En el ejemplo anterior, la propiedad Result es del tipo string (String en Visual Basic).
Sin embargo, en algunos casos le podra interesar crear una tarea dentro de otra y devolver la tarea
anidada. En este caso, TResult de la tarea que incluye es, en s misma, una tarea. En el siguiente
ejemplo, la propiedad Result es Task<Task<string>> en C# o Task(Of Task(Of String)) en Visual Basic.
' Note the type of t and t2.
Dim t As Task(Of Task(Of String)) = Task.Factory.StartNew(Function() DoWorkAsync())
Dim t2 As Task(Of Task(Of String)) = DoWorkAsync().ContinueWith(Function(s) DoMoreWorkAsync())
' Outputs: System.Threading.Tasks.Task`1[System.String]
Console.WriteLine(t.Result)
Aunque es posible para escribir el cdigo para desempaquetar la tarea exterior y recuperar la tarea
original y su propiedad Result, tal cdigo no es fcil de escribir porque debe administrar las
excepciones y tambin las solicitud de la cancelacin. En esta situacin, recomendamos utilizar uno
de los mtodos de extensin Unwrap, como se muestra en el siguiente ejemplo.
' Unwrap the inner task.
Dim t3 As Task(Of String) = DoWorkAsync().ContinueWith(Function(s) DoMoreWorkAsync()).Unwrap()
' Outputs "More work completed."
Console.WriteLine(t.Result)
Los mtodos Unwrap se pueden utilizar para transformar Task<Task> o Task<Task<TResult>>
(Task(Of Task) o Task(Of Task(Of TResult)) en Visual Basic) en Task o Task<TResult> (Task(Of TResult)
en Visual Basic). La nueva tarea representa al completo la tarea anidada interna e incluye el estado de
cancelacin y todas las excepciones.
Ejemplo
En el ejemplo siguiente se muestra cmo usar los mtodos de extensin Unwrap.
'How to: Unwrap a Task
Imports System.Threading
Imports System.Threading.Tasks
Module UnwrapATask2
Sub Main()
' An arbitrary threshold value.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 47 de 107
Dim threshold As Byte = &H40
' myData is a Task(Of Byte())
Dim myData As Task(Of Byte()) = Task.Factory.StartNew(Function()
Return GetData()
End Function)
' We want to return a task so that we can continue from it later in the program.
' Without Unwrap: stepTwo is a Task(Of Task(Of Byte)) With Unwrap: stepTwo is a Task(Of Byte)
Dim stepTwo = myData.ContinueWith(Function(antecedent)
Return Task.Factory.StartNew(Function()
Return Compute(antecedent.Result)
End Function)
End Function).Unwrap()
Dim lastStep = stepTwo.ContinueWith(Function(antecedent)
Console.WriteLine("Result = {0}", antecedent.Result)
If antecedent.Result >= threshold Then
Return Task.Factory.StartNew(Sub()
Console.WriteLine("Program complete. Final = &H{1:x} threshold =
&H{1:x}",
stepTwo.Result, threshold)
End Sub)
Else
Return DoSomeOtherAsynchronousWork(stepTwo.Result, threshold)
End If
End Function)
Try
lastStep.Wait()
Catch ae As AggregateException
For Each ex As Exception In ae.InnerExceptions
Console.WriteLine(ex.Message & ex.StackTrace & ex.GetBaseException.ToString())
Next
End Try
Console.WriteLine("Press any key")
Console.ReadKey()
End Sub
#Region "Dummy_Methods"
Function GetData() As Byte()
Dim rand As Random = New Random()
Dim bytes(64) As Byte
rand.NextBytes(bytes)
Return bytes
End Function
Function DoSomeOtherAsynchronousWork(ByVal i As Integer, ByVal b2 As Byte) As Task
Return Task.Factory.StartNew(Sub()
Thread.SpinWait(500000)
Console.WriteLine("Doing more work. Value was <= threshold.")
End Sub)
End Function
Function Compute(ByVal d As Byte()) As Byte
Dim final As Byte = 0
For Each item As Byte In d
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 48 de 107
final = final Xor item
Console.WriteLine("{0:x}", final)
Next
Console.WriteLine("Done computing")
Return final
End Function
#End Region
End Module

1.3. TPL con otros modelos asincrnicos
La biblioteca TPL (Task Parallel Library, biblioteca de procesamiento paralelo basado en tareas) se
puede usar con modelos tradicionales de programacin asincrnica de .NET de varias maneras.
1.3.1. TPL y la programacin asincrnica tradicional de .NET
.NET Framework proporciona los siguientes dos modelos estndar para realizar las operaciones
asincrnicas enlazadas a E/S y enlazadas a clculos:
Modelo de programacin asincrnica (APM), en el que las operaciones asincrnicas se
representan mediante un par de mtodos Begin/End como FileStream.BeginRead y
Stream.EndRead.
Modelo asincrnico basado en eventos (EAP), en el que las operaciones asincrnicas se
representan mediante un par mtodo-evento que se denomina OperationNameAsync y
OperationNameCompleted, por ejemplo, WebClient.DownloadStringAsync y
WebClient.DownloadStringCompleted. (EAP apareci por primera vez en .NET Framework
versin 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 los modelos asincrnicos. Puede exponer las
operaciones de APM y EAP como tareas a los consumidores de la biblioteca o puede exponer los
modelos de APM, pero usar objetos de tarea para implementarlos internamente. En ambos
escenarios, al usar los objetos de tarea, puede simplificar el cdigo y aprovechar la siguiente
funcionalidad til:
Registre las devoluciones de llamada, en el formulario de continuaciones de la tarea, en
cualquier momento despus de que se haya iniciado la tarea.
Coordine varias operaciones que se ejecutan en respuesta a un mtodo Begin_, mediante
los mtodos ContinueWhenAny, ContinueWhenAll, WaitAll o WaitAny.
Encapsule las operaciones asincrnicas enlazadas a E/S y enlazadas a clculos en el mismo
objeto de tarea.
Supervise el estado del objeto de tarea.
Calcule las referencias del estado una operacin para un objeto de tarea mediante
TaskCompletionSource(Of TResult).
Ajustar las operaciones de APM en una tarea
Las clases System.Threading.Tasks.TaskFactory(Of TResult) y System.Threading.Tasks.TaskFactory
proporcionan varias sobrecargas de los mtodos FromAsync que le permitieron encapsular un Begin
de APM/par de mtodo de Fin en uno Task crean instancias o instancia Task(Of TResult). Las diversas
sobrecargas hospedan cualquier par de mtodos de Begin/End que tenga entre cero y tres
parmetros de entrada.
Para los pares que tienen mtodos End que devuelven un valor (Function en Visual Basic), use los
mtodos de TaskFactory(Of TResult), que crean un objeto Task(Of TResult). Para los mtodos End
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 49 de 107
que devuelven un valor void (Sub en Visual Basic), use los mtodos de TaskFactory, que crean un
objeto Task.
En los pocos casos en los que el mtodo Begin tiene ms de tres parmetros o contiene parmetros
out o ref, se proporcionan las sobrecargas FromAsync adicionales que encapsulan slo el mtodo
End.
En el ejemplo de cdigo siguiente se muestra la signatura para la sobrecarga FromAsync que
coincide con los mtodos FileStream.BeginRead y FileStream.EndRead. Esta sobrecarga toma los tres
parmetros de entrada siguientes.
Public Function FromAsync(Of TArg1, TArg2, TArg3)(
ByVal beginMethod As Func(Of TArg1, TArg2, TArg3, AsyncCallback, Object, IAsyncResult),
ByVal endMethod As Func(Of IAsyncResult, TResult),
ByVal dataBuffer As TArg1,
ByVal byteOffsetToStartAt As TArg2,
ByVal maxBytesToRead As TArg3,
ByVal stateInfo As Object)
El primer parmetro es un delegado Func(Of T1, T2, T3, T4, T5, TResult) que coincide con la signatura
del mtodo FileStream.BeginRead. El segundo parmetro es un delegado Func(Of T, TResult) que
toma una interfaz IAsyncResult y devuelve TResult. Dado que EndRead devuelve un entero, el
compilador deduce el tipo de TResult como Int32 y el tipo de la tarea como Task(Of Int32). Los
ltimos cuatro parmetros son idnticos a los del mtodo FileStream.BeginRead:
Bfer donde se van a almacenar los datos de archivo.
El desplazamiento en el bfer en el que empezar a escribir los datos.
Cantidad mxima 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 devolucin de llamada.
Usar ContinueWith para la funcionalidad de devolucin de llamada
Si necesita obtener acceso a los datos del archivo, en contraposicin a solo el nmero de bytes, el
mtodo FromAsync no es suficiente. En su ligar, use Task(Of String), cuya propiedad Result contiene
los datos de archivo. Puede hacer si agrega una continuacin a la tarea original. La continuacin
realiza el trabajo que normalmente realizara el delegado AsyncCallback. Se invoca cuando se
completa el antecedente y se ha rellenado el bfer de datos. (El objeto FileStream se debera cerrar
antes de devolver un valor).
En el siguiente ejemplo se muestra cmo devolver un objeto Task(Of String) que encapsula el par
BeginRead/EndRead de la clase FileStream.
Const MAX_FILE_SIZE As Integer = 14000000
Shared Function GetFileStringAsync(ByVal path As String) As Task(Of String)
Dim fi As New FileInfo(path)
Dim data(fi.Length) As Byte
Dim fs As FileStream = New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, True)
' Task(Of Integer) returns the number of bytes read
Dim myTask As Task(Of Integer) = Task(Of Integer).Factory.FromAsync(
AddressOf fs.BeginRead, AddressOf fs.EndRead, data, 0, data.Length, Nothing)
' 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 myTask.ContinueWith(Function(antecedent)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 50 de 107
fs.Close()
If (antecedent.Result < 100) Then
Return "Data is too small to bother with."
End If
' If we did not receive the entire file, the end of the data buffer will contain garbage.
If (antecedent.Result < data.Length) Then
Array.Resize(data, antecedent.Result)
End If
' 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)
End Function)
End Function
A continuacin, se puede llamar al mtodo de la forma siguiente.
Dim myTask As Task(Of String) = GetFileStringAsync(path)
' Do some other work
' ...
Try
Console.WriteLine(myTask.Result.Substring(0, 500))
Catch ex As AggregateException
Console.WriteLine(ex.InnerException.Message)
End Try
Proporcionar los datos de estado personalizados
En las operaciones IAsyncResult tpicas, si el delegado AsyncCallback requiere algn dato de estado
personalizado, tiene que pasarlo a travs del ltimo parmetro Begin para que los datos se puedan
empaquetar en el objeto IAsyncResult que se pasar finalmente al mtodo de devolucin de llamada.
Normalmente no se requiere esto cuando se usan los mtodos FromAsync. Si los datos
personalizados son conocidos para la continuacin, se pueden capturar directamente en el delegado
de continuacin. El siguiente ejemplo se parece el ejemplo anterior, pero en lugar de examinar la
propiedad Result del antecedente, la continuacin examina los datos de estado personalizados que
son directamente accesibles al delegado de usuario de la continuacin.
Public Function GetFileStringAsync2(ByVal path As String) As Task(Of String)
Dim fi = New FileInfo(path)
Dim data(fi.Length) As Byte
Dim state As New MyCustomState()
Dim fs As 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.
Dim myTask As Task(Of Integer) = Task(Of Integer).Factory.FromAsync(
AddressOf fs.BeginRead, AddressOf fs.EndRead, data, 0, data.Length, Nothing)
Return myTask.ContinueWith(Function(antecedent)
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")) Then
Return "Start spreading the news!"
End If
' If we did not receive the entire file, the end of the data buffer will contain garbage.
If (antecedent.Result < data.Length) Then
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 51 de 107
Array.Resize(data, antecedent.Result)
End If
'/ 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)
End Function)
End Function
Sincronizar varias tareas FromAsync
Los mtodos estticos ContinueWhenAny y ContinueWhenAll proporcionan flexibilidad adicional
cuando se usan junto con los mtodos FromAsync. El siguiente ejemplo muestra cmo iniciar varias
operaciones asincrnicas de E/S y, a continuacin, espera a que todos ellas se completen antes de
ejecutar la continuacin.
Public Function GetMultiFileData(ByVal filesToRead As String()) As Task(Of String)
Dim fs As FileStream
Dim tasks(filesToRead.Length) As Task(Of String)
Dim fileData() As Byte = Nothing
For i As Integer = 0 To filesToRead.Length
fileData(&H1000) = New Byte()
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(Of Integer).Factory.FromAsync(AddressOf fs.BeginRead, AddressOf fs.EndRead, fileData, 0,
fileData.Length, Nothing).
ContinueWith(Function(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) Then
ReDim Preserve fileData(antecedent.Result)
End If
'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)
End Function)
Next
Return Task(Of String).Factory.ContinueWhenAll(tasks, Function(data)
' Propagate all exceptions and mark all faulted tasks as observed.
Task.WaitAll(data)
' Combine the results from all tasks.
Dim sb As New StringBuilder()
For Each t As Task(Of String) In data
sb.Append(t.Result)
Next
' Final result to be returned eventually on the calling thread.
Return sb.ToString()
End Function)
End Function
Tareas FromAsync solo para el mtodo End
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 52 de 107
En los pocos casos en los que el mtodo Begin requiere ms de tres parmetros de entrada o tiene
parmetros out o ref, puede usar las sobrecargas FromAsync, por ejemplo, TaskFactory(Of
TResult).FromAsync(IAsyncResult, Func(Of IAsyncResult, TResult)), que representa slo el mtodo
End. Estos mtodos tambin se pueden usar en cualquier escenario en el que se pasa IAsyncResult y
desea encapsularlo en una tarea.
Shared Function ReturnTaskFromAsyncResult() As Task(Of String)
Dim ar As IAsyncResult = DoSomethingAsynchronously()
Dim t As Task(Of String) = Task(Of String).Factory.FromAsync(ar, Function(res) CStr(res.AsyncState))
Return t
End Function
Iniciar y cancelar las tareas FromAsync
La tarea devuelta por un mtodo FromAsync tiene un estado de WaitingForActivation y la iniciar el
sistema en algn momento una vez creada la tarea. Si intenta llamar a Start en este tipo de tarea, se
producir una excepcin.
No puede cancelar una tarea FromAsync, porque las API subyacentes de .NET Framework admiten
actualmente la cancelacin en curso del la E/S de archivo o red. Puede agregar la funcionalidad de
cancelacin a un mtodo que encapsula una llamada FromAsync, pero slo puede responder a la
cancelacin antes de que se llame a FromAsync o despus de completar (por ejemplo, en una tarea
de continuacin).
Algunas clases que admiten EAP, por ejemplo, WebClient, admiten la cancelacin y esa funcionalidad
de cancelacin nativa se puede integrar mediante los tokens de cancelacin.
Exponer las operaciones de EAP complejas como tareas
La TPL no proporciona ningn mtodo diseado especficamente para encapsular una operacin
asincrnica basada en eventos del mismo modo que la familia de mtodos FromAsync ajusta el
modelo IAsyncResult. Sin embargo, TPL proporciona la clase
System.Threading.Tasks.TaskCompletionSource(Of TResult), que se puede usar para representar
cualquier conjunto arbitrario de operaciones como Task(Of TResult). Las operaciones pueden ser
sincrnicas o asincrnicas y pueden ser enlazadas a E/S o enlazadas a clculo, o ambos.
En el siguiente ejemplo se muestra cmo usar TaskCompletionSource(Of TResult) para exponer un
conjunto de operaciones WebClient asincrnicas al cdigo de cliente como un objeto Task bsico. El
mtodo permite escribir una matriz de direcciones URL de web y un trmino o nombre que se va a
buscar y, a continuacin, devuelve el nmero de veces que aparece el trmino de bsqueda en cada
sitio.
Class SimpleWebExample
Dim tcs As New TaskCompletionSource(Of String())
Dim nameToSearch As String
Dim token As CancellationToken
Dim results As New List(Of String)
Dim m_lock As Object
Dim count As Integer
Dim addresses() As String
Public Function GetWordCountsSimplified(ByVal urls() As String, ByVal str As String, ByVal token As
CancellationToken) As Task(Of String())
Dim webClients() As WebClient
ReDim webClients(urls.Length)
' If the user cancels the CancellationToken, then we can use the
' WebClient's ability to cancel its own async operations.
token.Register(Sub()
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 53 de 107
For Each wc As WebClient In webClients
If Not wc Is Nothing Then
wc.CancelAsync()
End If
Next
End Sub)
For i As Integer = 0 To urls.Length
webClients(i) = New WebClient()
' Specify the callback for the DownloadStringCompleted
' event that will be raised by this WebClient instance.
AddHandler webClients(i).DownloadStringCompleted, AddressOf WebEventHandler
Dim address As 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)
Next
' Return the underlying Task. The client code
' waits on the Result property, and handles exceptions
' in the try-catch block there.
Return tcs.Task
End Function
Public Sub WebEventHandler(ByVal sender As Object, ByVal args As DownloadStringCompletedEventArgs)
If args.Cancelled = True Then
tcs.TrySetCanceled()
Return
ElseIf Not args.Error Is Nothing Then
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.
Dim words() As String = args.Result.Split(" "c)
Dim NAME As String = nameToSearch.ToUpper()
Dim 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))
End If
SyncLock (m_lock)
count = count + 1
If (count = addresses.Length) Then
tcs.TrySetResult(results.ToArray())
End If
End SyncLock
End Sub
End Class
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 54 de 107
Recuerde que TaskCompletionSource iniciar cualquier tarea creada por TaskCompletionSource(Of
TResult) y, por consiguiente, el cdigo de usuario no debera llamar al mtodo 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 mtodos Begin/End en una API. Por ejemplo, quizs desee mantener la coherencia con las
API existentes o puede haber automatizado herramientas que requieren este modelo. En tales casos,
puede usar las tareas para simplificar la forma en que se implementa internamente el modelo de
APM.
En el siguiente ejemplo se muestra cmo usar las tareas para implementar un par de mtodos
Begin/End de APM para un mtodo enlazado a clculo de ejecucin prolongada.
Class Calculator
Public Function BeginCalculate(ByVal decimalPlaces As Integer, ByVal ac As AsyncCallback, ByVal state As Object)
As IAsyncResult
Console.WriteLine("Calling BeginCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId)
Dim myTask = Task(Of String).Factory.StartNew(Function(obj) Compute(decimalPlaces), state)
myTask.ContinueWith(Sub(antedecent) ac(myTask))
End Function
Private Function Compute(ByVal decimalPlaces As Integer)
Console.WriteLine("Calling compute on thread {0}", Thread.CurrentThread.ManagedThreadId)
' Simulating some heavy work.
Thread.SpinWait(500000000)
' Actual implemenation left as exercise for the reader. Several examples are available on the Web.
Return "3.14159265358979323846264338327950288"
End Function
Public Function EndCalculate(ByVal ar As IAsyncResult) As String
Console.WriteLine("Calling EndCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId)
Return CType(ar, Task(Of String)).Result
End Function
End Class
Class CalculatorClient
Shared decimalPlaces As Integer
Shared Sub Main()
Dim calc As New Calculator
Dim places As Integer = 35
Dim callback As New AsyncCallback(AddressOf PrintResult)
Dim ar As IAsyncResult = calc.BeginCalculate(places, callback, calc)
' Do some work on this thread while the calulator is busy.
Console.WriteLine("Working...")
Thread.SpinWait(500000)
Console.ReadLine()
End Sub
Public Shared Sub PrintResult(ByVal result As IAsyncResult)
Dim c As Calculator = CType(result.AsyncState, Calculator)
Dim piString As String = c.EndCalculate(result)
Console.WriteLine("Calling PrintResult on thread {0}; result = {1}",
Thread.CurrentThread.ManagedThreadId, piString)
End Sub
End Class
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 55 de 107
Usar el cdigo de ejemplo de StreamExtensions
El archivo Streamextensions.cs, en la pgina Samples for Parallel Programming with the .NET
Framework 4 del sitio web de MSDN, contiene varias implementaciones de la referencia que usan los
objetos de tarea para la E/S asincrnica de archivo y red.
1.3.2. Cmo: Encapsular modelos de EAP en una tarea
En el siguiente ejemplo se muestra cmo exponer las operaciones asincrnicas a una secuencia
arbitraria de Protocolo de autenticacin extensible Host (EAP) como una tarea utilizando
TaskCompletionSource(Of TResult). El ejemplo tambin muestra cmo usar CancellationToken para
invocar los mtodos de cancelacin integrados en los objetos WebClient.
Ejemplo
Class WebDataDownLoader
Dim tcs As New TaskCompletionSource(Of String())
Dim nameToSearch As String
Dim token As CancellationToken
Dim results As New List(Of String)
Dim m_lock As Object
Dim count As Integer
Dim addresses() As String
Shared Sub Main()
Dim downloader As New WebDataDownLoader()
downloader.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"}
Dim cts As New CancellationTokenSource()
' Create a UI thread from which to cancel the entire operation
Task.Factory.StartNew(Sub()
Console.WriteLine("Press c to cancel")
If Console.ReadKey().KeyChar = "c"c Then
cts.Cancel()
End If
End Sub)
' Using a neutral search term that is sure to get some hits on English web sites.Please substitute your favorite search
term.
downloader.nameToSearch = "the"
Dim webTask As Task(Of String()) = downloader.GetWordCounts(downloader.addresses,
downloader.nameToSearch, cts.Token)
' Do some other work here unless the method has already completed.
If (webTask.IsCompleted = False) Then
' Simulate some work
Thread.SpinWait(5000000)
End If
Dim results As String() = Nothing
Try
results = webTask.Result
Catch ae As AggregateException
For Each ex As Exception In ae.InnerExceptions
If (TypeOf (ex) Is OperationCanceledException) Then
Dim oce As OperationCanceledException = CType(ex, OperationCanceledException)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 56 de 107
If oce.CancellationToken = cts.Token Then
Console.WriteLine("Operation canceled by user.")
End If
Else
Console.WriteLine(ex.Message)
End If
Next
End Try
If (Not results Is Nothing) Then
For Each item As String In results
Console.WriteLine(item)
Next
End If
Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub
Public Function GetWordCounts(ByVal urls() As String, ByVal str As String, ByVal token As CancellationToken) As
Task(Of String())
Dim webClients() As WebClient
ReDim webClients(urls.Length)
m_lock = New Object()
' If the user cancels the CancellationToken, then we can use the WebClient's ability to cancel its own async
operations.
token.Register(Sub()
For Each wc As WebClient In webClients
If Not wc Is Nothing Then
wc.CancelAsync()
End If
Next
End Sub)
For i As Integer = 0 To urls.Length - 1
webClients(i) = New WebClient()
' Specify the callback for the DownloadStringCompleted event that will be raised by this WebClient instance.
AddHandler webClients(i).DownloadStringCompleted, AddressOf WebEventHandler
Dim address As Uri = Nothing
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 ex As UriFormatException
tcs.TrySetException(ex)
Return tcs.Task
End Try
Next
' Return the underlying Task. The client code waits on the Result property, and handles exceptions
' in the try-catch block there.
Return tcs.Task
End Function
Public Sub WebEventHandler(ByVal sender As Object, ByVal args As DownloadStringCompletedEventArgs)
If args.Cancelled = True Then
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 57 de 107
tcs.TrySetCanceled()
Return
ElseIf Not args.Error Is Nothing Then
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.
Dim words() As String = args.Result.Split(" "c)
Dim NAME As String = nameToSearch.ToUpper()
Dim 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, nameToSearch))
End If
SyncLock (m_lock)
count = count + 1
If (count = addresses.Length) Then
tcs.TrySetResult(results.ToArray())
End If
End SyncLock
End Sub

1.4. Problemas potenciales en el paralelismo de datos y tareas
En muchos casos, Parallel.For y Parallel.ForEach pueden proporcionar mejoras de rendimiento
significativas en comparacin con los bucles secuenciales normales. Sin embargo, el trabajo de
paralelizar el bucle aporta una complejidad que puede llevar a problemas que, en cdigo secuencial,
no son tan comunes o que no se producen en absoluto. En este tema se indican algunas prcticas
que se deben evitar al escribir bucles paralelos.
No se debe suponer que la ejecucin en paralelo es siempre ms rpida
En algunos casos, un bucle paralelo se podra ejecutar ms lentamente que su equivalente secuencial.
La regla bsica es que no es probable que los bucles en paralelo que tienen pocas iteraciones y
delegados de usuario rpidos 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 cdigo secuencial, no es raro leer o escribir en variables estticas o en campos de clase. Sin
embargo, cuando varios subprocesos tienen acceso a estas variables de forma simultnea, hay
grandes posibilidades de que se produzcan condiciones de carrera. Aunque se pueden usar bloqueos
para sincronizar el acceso a la variable, el costo de la sincronizacin puede afectar negativamente al
rendimiento. Por tanto, se recomienda evitar, o al menos limitar, el acceso al 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.ForEach que utilizan una variable
System.Threading.ThreadLocal(Of T) para almacenar el estado local del subproceso durante la
ejecucin del bucle.
Evitar la paralelizacin excesiva
Si usa bucles en paralelo, incurrir en costos de sobrecarga al crear particiones de la coleccin de
origen y sincronizar los subprocesos de trabajo. El nmero de procesadores del equipo reduce
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 58 de 107
tambin las ventajas de la paralelizacin. Si se ejecutan varios subprocesos enlazados a clculos en
un nico procesador, no se gana en velocidad. Por tanto, debe tener cuidado para no paralelizar en
exceso un bucle.
El escenario ms comn en el que se puede producir un exceso de paralelizacin son los bucles
anidados. En la mayora de los casos, es mejor paralelizar nicamente el bucle exterior, a menos que
se cumplan una o ms de las siguientes condiciones:
Se sabe que el bucle interno es muy largo.
Se realiza un clculo caro en cada pedido. (La operacin que se muestra en el ejemplo no es
cara.)
Se sabe que el sistema de destino tiene suficientes procesadores como para controlar el
nmero de subprocesos que se producirn 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 medicin.
Evitar llamadas a mtodos que no son seguros para subprocesos
La escritura en mtodos de instancia que no son seguros para subprocesos de un bucle en paralelo
puede producir daos en los datos, que pueden pasar o no inadvertidos para el programa. Tambin
puede dar lugar a excepciones. En el siguiente ejemplo, varios subprocesos estaran intentando
llamar simultneamente al mtodo FileStream.WriteByte, que no se admite en la clase.
Dim fs As FileStream = File.OpenWrite(filepath)
Dim bytes() As Byte
ReDim bytes(1000000)
' ...init byte array
Parallel.For(0, bytes.Length, Sub(n) fs.WriteByte(bytes(n)))
Limitar las llamadas a mtodos seguros para subprocesos
La mayora de los mtodos estticos de .NET Framework son seguros para subprocesos y se les
pueden llamar simultneamente desde varios subprocesos. Sin embargo, incluso en estos casos, la
sincronizacin que esto supone puede conducir a una ralentizacin importante en la consulta.
Nota
Puede comprobarlo si inserta algunas llamadas a WriteLine en las consultas. Aunque este mtodo se
utiliza en los ejemplos de la documentacin para los efectos de demostracin, no lo utilice en bucles
paralelos a menos que necesario.
Ser consciente de los problemas de afinidad de los subprocesos
Algunas tecnologas, como la interoperabilidad COM para componentes STA (apartamento de un
nico subproceso), Windows Forms y Windows Presentation Foundation (WPF), imponen
restricciones de afinidad de subprocesos que exigen que el cdigo se ejecute en un subproceso
determinado. Por ejemplo, tanto en 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 un
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 de usuario.
Tener precaucin 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 actualmente. Esta optimizacin de rendimiento puede
acabar en interbloqueo en algunos casos. Por ejemplo, dos tareas podran ejecutar el mismo cdigo
de delegado, que seala cundo se genera un evento, y despus esperar a que la otra tarea seale. Si
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 59 de 107
la segunda tarea est alineada en el mismo subproceso que la primera y la primero entra en un
estado de espera, la segunda tarea nunca podr sealar su evento. Para evitar que suceda, puede
especificar un tiempo de espera en la operacin de espera o utilizar constructores de subproceso
explcitos para ayudar a asegurarse 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(Of
TSource) tal vez no tengan que ejecutarse en paralelo. Por consiguiente, se debe evitar escribir
cdigo cuya exactitud dependa de la ejecucin en paralelo de las iteraciones o de la ejecucin de las
iteraciones en algn orden concreto. Por ejemplo, es probable que este cdigo lleve a un
interbloqueo:
Dim mres = New ManualResetEventSlim()
Enumerable.Range(0, Environment.ProcessorCount * 100) _
.AsParallel() _
.ForAll(Sub(j)
If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Wait()
End If
End Sub) ' deadlocks
En este ejemplo, una iteracin establece un evento y el resto de las iteraciones esperan el evento.
Ninguna de las iteraciones que esperan puede completarse hasta que se haya completado la
iteracin del valor de evento. Sin embargo, es posible que las iteraciones que esperan bloqueen
todos los subprocesos que se utilizan para ejecutar el bucle paralelo, antes de que la iteracin del
valor de evento haya tenido oportunidad de ejecutarse. Esto produce un interbloqueo: la iteracin
del valor de evento nunca se ejecutar y las iteraciones que esperan nunca se activarn.
En concreto, una iteracin de un bucle paralelo no debe esperar nunca otra iteracin del bucle para
progresar. Si el bucle paralelo decide programar las iteraciones secuencialmente pero en el orden
contrario, se producir un interbloqueo.
Evitar la ejecucin de bucles en paralelo en el subproceso de la interfaz de
usuario
Es importante mantener la interfaz de usuario de la aplicacin (UI) capaz de reaccionar. Si una
operacin contiene bastante trabajo para garantizar la paralelizacin, no se debera ejecutar en el
subproceso de la interfaz de usuario. Conviene descargarla para que se ejecute en un subproceso en
segundo plano. Por ejemplo, si desea utilizar un bucle paralelo para calcular datos que despus se
presentarn en un control de IU, considere ejecutar el bucle dentro de una instancia de la tarea, en
lugar de directamente en un controlador de eventos de IU. Solo si el clculo bsico se ha completado
se deberan calcular las referencias de la actualizacin 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
actualizacin de los controles de la interfaz de usuario desde el 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 la interfaz de usuario, se puede llegar a daar el estado, a producir
excepciones, actualizaciones atrasadas e incluso interbloqueos, dependiendo de cmo se invoque la
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 60 de 107
actualizacin 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 iteracin del bucle en un subproceso en segundo
plano (como puede hacer For), la llamada a Invoke produce que se enve un mensaje al subproceso
de la interfaz de usuario, que se bloquea mientras espera a que ese 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 la interfaz de usuario se interbloquea.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim iterations As Integer = 20
Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub)
End Sub
En el siguiente ejemplo se muestra cmo evitar el interbloqueo mediante la ejecucin 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 Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim iterations As Integer = 20
Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub))
End Sub

2. Parallel LINQ (PLINQ)
Parallel LINQ (PLINQ) es una implementacin paralela de LINQ to Objects. PLINQ implementa el
conjunto completo de operadores de consulta estndar de LINQ como mtodos de extensin para el
espacio de nombres T:System.Linq y tiene operadores adicionales para las operaciones paralelas.
PLINQ combina la simplicidad y legibilidad de la sintaxis de LINQ con la eficacia de la programacin
paralela. De la misma forma que el cdigo destinado a la biblioteca TPL (Task Parallel Library,
biblioteca de procesamiento paralelo basado en tareas), las consultas PLINQ aumentan el grado de
simultaneidad en funcin de la capacidad del equipo host.
En muchos escenarios, PLINQ puede aumentar significativamente la velocidad de las consultas LINQ
to Objects utilizando todos los ncleos disponibles en el equipo host de una forma ms eficaz. Este
mayor rendimiento aporta al escritorio una alta capacidad de computacin.
2.1. Introduccin a PLINQ
Qu es una consulta paralela?
Language-Integrated Query (LINQ) apareci por primera vez en .NET Framework 3.0. Representa un
modelo unificado para consultar cualquier origen de datos System.Collections.IEnumerable o
System.Collections.Generic. IEnumerable(Of T) con seguridad de tipos. LINQ to Objects es el nombre
para los consultas LINQ que se ejecutan con las colecciones en memoria, como List(Of T), y las
matrices. En este artculo se supone que tiene un conocimiento bsico de LINQ.
Parallel LINQ (PLINQ) es una implementacin paralela del modelo LINQ. Una consulta PLINQ se
parece de muchas maneras a una consulta LINQ to Objects no paralela. Las consultas PLINQ, al igual
que las consultas LINQ secuenciales, funcionan con cualquier origen de datos IEnumerable o
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 61 de 107
IEnumerable(Of T) en memoria y usan la ejecucin diferida, lo que significa que no empiezan a
ejecutarse hasta que se enumere la consulta. La diferencia primaria es que PLINQ intenta realizar el
uso completo de todos los procesadores en el sistema. Para hacer esto, divide el origen de datos en
segmentos y, a continuacin, ejecuta la consulta en cada segmento en subprocesos de trabajo
independientes en paralelo en varios procesadores. En muchos casos, la ejecucin en paralelo
significa que la consulta se ejecuta bastante ms rpidamente.
A travs de la ejecucin en paralelo, PLINQ puede lograr mejoras de rendimiento significativas
respecto al cdigo heredado para ciertos tipos de consultas, a menudo con solo agregar la operacin
de consulta AsParallel al origen de datos. Sin embargo, el paralelismo puede presentar sus propias
complejidades y no todas las operaciones de consulta se ejecutan ms rpidamente en PLINQ. De
hecho, la ejecucin en paralelo realmente reduce la velocidad de ciertas consultas. Por consiguiente,
debera entender en qu grado afectan a las consultas en paralelo ciertos aspectos como la
ordenacin.
El resto de este artculo proporciona informacin general de las clases PLINQ principales y describe
cmo crear las consultas PLINQ. Cada seccin contiene vnculos a ejemplos de cdigo e informacin
ms detallada.
La clase ParallelEnumerable
La clase System.Linq.ParallelEnumerable expone casi toda la funcionalidad de PLINQ. Esta clase y el
resto de tipos del espacio de nombres System.Linq se compilan en el ensamblado System.Core.dll.
Los proyectos predeterminados de C# y Visual Basic en Visual Studio hacen referencia el ensamblado
e importan el espacio de nombres.
ParallelEnumerable incluye implementaciones de todos los operadores de consulta estndar que
admite LINQ to Objects, aunque no intenta ejecutar cada una en paralelo.
Adems de los operadores de consulta estndar, la clase ParallelEnumerable contiene un conjunto de
mtodos que habilitan los comportamientos especficos de la ejecucin en paralelo. Estos mtodos
especficos de PLINQ se muestran en la siguiente tabla.
Operador
ParallelEnumerable
Descripcin
AsParallel
Punto de entrada para PLINQ. Especifica que el resto de la consulta se
debera ejecutar en paralelo, si es posible.
AsSequential(Of TSource)
Especifica que el resto de la consulta se debera ejecutar
secuencialmente, como un consulta LINQ no paralela.
AsOrdered
Especifica que PLINQ debera conservar la clasificacin de la secuencia
de origen para el resto de la consulta, o hasta que se cambie la
clasificacin, por ejemplo mediante una clusula orderby (Order By en
Visual Basic).
AsUnordered(Of TSource)
Especifica que no es necesario que PLINQ conserve la clasificacin de
la secuencia de origen durante el resto de la consulta.
WithCancellation(Of TSource)
Especifica que PLINQ debera supervisar peridicamente el estado del
token de cancelacin proporcionado y cancelar la ejecucin si se
solicita.
WithDegreeOfParallelism(Of
TSource)
Especifica el nmero mximo de procesadores que PLINQ debera
usar para ejecutar la consulta en paralelo.
WithMergeOptions(Of
TSource)
Proporciona una sugerencia sobre cmo PLINQ debe, si es posible,
volver a combinar los resultados paralelos en solo una secuencia en el
subproceso utilizado.
WithExecutionMode(Of Especifica si PLINQ debera ejecutar la consulta en paralelo incluso
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 62 de 107
TSource) cuando el comportamiento predeterminado sera ejecutarla
secuencialmente.
ForAll(Of TSource)
Un mtodo de enumeracin multiproceso que, a diferencia de iterar
por los resultados de la consulta, permite volver a procesar los
resultados en paralelo sin volver a combinar primero el subproceso de
consumidor.
Sobrecarga Aggregate
Una sobrecarga que es exclusiva de PLINQ y habilita la agregacin
intermedia sobre las particiones locales de subprocesos, ms una
ltima funcin de agregacin para combinar los resultados de todas
las particiones.
El modelo de opcin
Al escribir una consulta, elija PLINQ invocando el mtodo de extensin ParallelEnumerable.AsParallel
en el origen de datos, como se muestra en el siguiente ejemplo.
Dim source = Enumerable.Range(1, 10000)
' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
Where Compute(num) > 0
Select num
El mtodo de extensin AsParallel enlaza a los operadores de consulta subsiguientes, en este caso,
where y select, a las implementaciones de System.Linq.ParallelEnumerable.
Modos de ejecucin
De forma predeterminada, PLINQ es conservador. En tiempo de ejecucin, la infraestructura PLINQ
analiza la estructura general de la consulta. Si es probable que la consulta produzca aumentos de
velocidad por la ejecucin en paralelo, PLINQ divide la secuencia de origen en tareas que se pueden
ejecutar simultneamente. Si no es seguro para ejecutar una consulta en paralelo, PLINQ
simplemente ejecuta la consulta de forma secuencial. Si PLINQ puede elegir entre un algoritmo
paralelo potencialmente costoso o un algoritmo secuencial poco costoso, elige el algoritmo
secuencial de forma predeterminada. Puede usar el mtodo WithExecutionMode(Of TSource) y la
enumeracin System.Linq.ParallelExecutionMode para indicar a PLINQ que seleccione el algoritmo
paralelo. Esto resulta til cuando sabe por las pruebas y mediciones que una consulta determinada se
ejecuta ms rpidamente en paralelo.
Grado de paralelismo
De forma predeterminada, PLINQ usa todos los procesadores en el equipo host hasta un mximo de
64. Puede indicar a PLINQ que no utilice ms que un nmero especificado de procesadores usando el
mtodo WithDegreeOf Parallelism(Of TSource). Esto resulta til si desea asegurarse de que los otros
procesos que se ejecutan en el equipo reciben cierta cantidad de tiempo de CPU. El siguiente
fragmento de cdigo limita la consulta a usar un mximo de dos procesadores.
Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)
Where Compute(item) > 42
Select item
En los casos donde una consulta est realizando una cantidad significativa de trabajo enlazado sin
clculo, como la E/S de archivo, podra ser beneficioso especificar un grado de paralelismo mayor
que el nmero de ncleos del equipo.
Consultar en paralelo ordenadas frente a no ordenadas
En algunas consultas, un operador de consulta debe generar resultados que conservan el orden de la
secuencia de origen. PLINQ proporciona al operador AsOrdered para este propsito. AsOrdered es
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 63 de 107
distinto de AsSequential(Of TSource). Una secuencia AsOrdered todava se procesa en paralelo, pero
sus resultados estn almacenados en bfer y ordenan. Dado que la preservacin del orden implica el
trabajo extraordinario normalmente, se podra procesar una secuencia AsOrdered ms despacio que
la secuencia AsUnordered(Of TSource) predeterminada. El hecho de que una operacin en paralelo
ordenada especial sea ms rpida que una versin secuencial de la operacin depende de muchos
factores.
El siguiente ejemplo de cdigo muestra las opciones que se deben elegir para conservar el orden.
Dim evenNums = From num In numbers.AsParallel().AsOrdered()
Where num Mod 2 = 0
Select num
Consultas enparalelo frente a secuenciales
Algunas operaciones requieren que los datos de origen se entreguen de una manera secuencial. Los
operadores de consulta ParallelEnumerable revierten automticamente al modo secuencial cuando es
necesario. Para los operadores de consulta definidos por el usuario y los delegados de usuario que
requieren la ejecucin secuencial, PLINQ proporciona el mtodo AsSequential(Of TSource). Al usar
AsSequential(Of TSource), se ejecutan secuencialmente todos los operadores subsiguientes en la
consulta hasta que se llame de nuevo a AsParallel.
Opciones para combinar los resultados de la consulta
Cuando una consulta PLINQ se ejecuta en paralelo, sus resultados de cada subproceso de trabajo
deben volver a combinarse en el subproceso principal para su uso en un bucle foreach (For Each en
Visual Basic) o la insercin en una lista o matriz. En algunos casos, podra ser beneficioso especificar
un tipo determinado de operacin de combinacin, por ejemplo, para empezar a generar resultados
ms rpidamente. Para este propsito, PLINQ admite el mtodo WithMergeOptions(Of TSource)y la
enumeracin ParallelMergeOptions.
El operador ForAll
En consultas LINQ secuenciales, la ejecucin se difiere hasta que la consulta se enumere o en un
bucle foreach (For Each en Visual Basic) o invocando un mtodo como ToList(Of TSource), ToArray(Of
TSource)o ToDictionary. En PLINQ, tambin se puede usar foreach para ejecutar la consulta e iterar
por los resultados. Sin embargo, el propio bucle foreach no se ejecuta en paralelo y, por
consiguiente, requiere que el resultado de todas las tareas paralelas se vuelva a combinar en el
subproceso en el que se est ejecutando el bucle. En PLINQ, puede usar foreach cuando deba
conservar el orden final de los resultados de la consulta, y tambin cada vez que procese los
resultados en serie, por ejemplo cuando est llamando a Console.WriteLine para cada elemento. Para
una ejecucin ms rpida de la consulta cuando no se requiere la conservacin del orden y cuando el
propio procesamiento de los resultados se puede ejecutar en paralelo, use el mtodo ForAll(Of
TSource) para ejecutar una consulta PLINQ. ForAll(Of TSource) no realiza este paso final de la
combinacin. En el siguiente ejemplo de cdigo, se muestra cmo utilizar el mtodo ForAll(Of
TSource). Se usa System.Collections.Concurrent.ConcurrentBag(Of T) en este caso porque est
optimizado para varios subprocesos que agregan elementos simultneamente sin intentar quitar
ningn elemento.
Dim nums = Enumerable.Range(10, 10000)
Dim query = From num In nums.AsParallel()
Where num Mod 10 = 0
Select num
' Process the results as each thread completes
' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 64 de 107
En la siguiente ilustracin, se muestra la diferencia entre foreach y ForAll(Of TSource) con respecto a
la ejecucin de una consulta.

Cancelacin
PLINQ se integra con los tipos de cancelacin en .NET Framework 4. (Para obtener ms informacin,
vea Cancelacin). Por consiguiente, a diferencia de las consultas secuenciales LINQ to Objects, las
consultas PLINQ pueden cancelarse. Crear una consulta PLINQ cancelable, use el operador
WithCancellation(Of TSource) en la consulta y proporcione una instancia de CancellationToken como
argumento. Cuando se establece en true la propiedad IsCancellationRequested del token, PLINQ lo
observar, detendr el procesamiento en todos los subprocesos y generar una excepcin
OperationCanceledException.
Es posible que una consulta PLINQ contine procesando algunos elementos una vez establecido el
token de cancelacin.
Para lograr una sensibilidad mayor, puede responder tambin a las solicitudes de cancelacin en los
delegados de usuario de ejecucin prolongada.
Excepciones
Cuando se ejecuta una consulta PLINQ, se podran producir simultneamente varias excepciones de
diferentes subprocesos. Asimismo, el cdigo para controlar la excepcin podra encontrarse un
subproceso diferente al del cdigo que produjo la excepcin. PLINQ usa el tipo AggregateException
para encapsular todas las excepciones producidas por una consulta y vuelve a calcular las referencias
de esas excepciones para el subproceso que realiza la llamada. En el subproceso que realiza la
llamada, solo se requiere un bloque try-catch. Sin embargo, puede iterar por todas las excepciones
encapsuladas en AggregateException y detectar cualquiera de la que se pueda recuperar de forma
segura. En casos raros, algunas excepciones se puede producir que no se ajusta en un
AggregateExceptiony tampoco se ajusta ThreadAbortExceptions.
Cuando las excepciones pueden propagarse de nuevo al subproceso de unin, es posible que una
consulta contine procesando algunos elementos despus de que se haya producido la excepcin.
Particionadores personalizados
En ciertos casos, puede mejorar el rendimiento de las consultas si escribe un particionador
personalizado que se aprovecha de alguna caracterstica de los datos de origen. En la consulta, el
propio particionador personalizado es el objeto enumerable que se consulta.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 65 de 107
Dim arr(10000) As Integer
Dim partitioner = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))
PLINQ admite un nmero de particiones fijo (aunque los datos se pueden reasignar dinmicamente a
esas particiones durante el tiempo de ejecucin para el equilibrio de carga). For y ForEach solo
admiten la creacin dinmica de particiones, lo que significa que el nmero de particiones cambia en
tiempo de ejecucin.
Medir el rendimiento de PLINQ
En muchos casos, una consulta se puede ejecutar en paralelo, pero la sobrecarga de preparar la
consulta paralela supera a la ventaja de rendimiento obtenida. Si una consulta no realiza muchos
clculos o si el origen de datos es pequeo, una consulta PLINQ puede ser ms lenta que una
consulta secuencial LINQ to Objects. Puede usar el Analizador de rendimiento de procesamiento
paralelo en Visual Studio Team Server para comparar el rendimiento de varias consultas, buscar los
cuellos de botella del procesamiento y determinar si su consulta se est ejecutando en paralelo o
secuencialmente.
2.2. Introduccin a la velocidad en PLINQ
El objetivo principal de PLINQ es acelerar la ejecucin de LINQ to Objects mediante la ejecucin de
los delegados de consulta en paralelo en equipos multiprocesador. El rendimiento de PLINQ es
ptimo cuando el procesamiento de cada elemento de una coleccin de origen es independiente, y
no se comparte el estado entre los delegados individuales. Tales operaciones son comunes en LINQ a
Objects y PLINQ y se llaman a menudo "encantadamente paralelo" porque se prestan con facilidad a
la programacin en varios subprocesos. Sin embargo, no todas las consultas se componen de
operaciones paralelas perfectas; en la mayora de los casos, una consulta incluye operadores que no
se pueden paralelizar o que ralentizan la ejecucin en paralelo. Incluso con consultas que son
perfectamente paralelas, PLINQ debe crear particiones del origen de datos y programar el trabajo en
los subprocesos, y generalmente tiene que combinar los resultados cuando la consulta se completa.
Todas estas operaciones aumentan el costo computacional de la paralelizacin; el costo de agregar
paralelizacin se denomina sobrecarga. Para lograr el rendimiento ptimo en una consulta PLINQ, el
objetivo es maximizar las partes que son perfectamente paralelas y minimizar las que requieren
sobrecarga. En este artculo se proporciona informacin que le ayudar a escribir consultas PLINQ lo
ms eficaces posible y que adems produzcan resultados correctos.
Factores que afectan al rendimiento de las consultas PLINQ
En las siguientes secciones se enumeran algunos de los factores ms importantes que influyen en el
rendimiento de las consultas en paralelo. Son instrucciones generales que por s solas no bastan para
predecir el rendimiento de las consultas en todos los casos. Como siempre, es importante medir el
rendimiento real de consultas concretas en equipos con una gama de cargas y configuraciones
representativas.
1. Costo computacional del trabajo total.
Para lograr velocidad, una consulta PLINQ debe tener bastante trabajo perfectamente en
paralelo como para compensar la sobrecarga. El trabajo se puede expresar como el costo
computacional de cada delegado multiplicado por el nmero de elementos de la coleccin
de origen. Suponiendo que una operacin se pueda paralelizar, cuanto ms cara sea
computacionalmente, ms oportunidad hay de aumentar la velocidad. Por ejemplo, si una
funcin tarda un milisegundo en ejecutarse, una consulta secuencial de ms de 1000
elementos tardar un segundo en realizar esa operacin, mientras que una consulta paralela
en un equipo con cuatro ncleos solo tardara 250 milisegundos. Esto supone 750
milisegundos menos. Si la funcin tardara un segundo en ejecutar cada elemento, el
aumento sera de 750 segundos. Si el delegado resulta muy caro, PLINQ podra proporcionar
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 66 de 107
un aumento significativo con solo unos elementos de la coleccin de origen. A la inversa, las
colecciones de origen pequeas con delegados triviales no son, en general, buenas
candidatas para PLINQ.
En el siguiente ejemplo, queryA es probablemente buena candidata para PLINQ, suponiendo
que su funcin Select implica mucho trabajo. queryB no es probablemente una buena
candidata porque no hay bastante trabajo en la instruccin Select y la sobrecarga de
paralelizacin compensar la mayora del aumento (o todo).
Dim queryA = From num In numberList.AsParallel()
Select ExpensiveFunction(num); 'good for PLINQ
Dim queryB = From num In numberList.AsParallel()
Where num Mod 2 > 0
Select num; 'not as good for PLINQ
Este idioma no es compatible o no hay ningn ejemplo de cdigo disponible.
2. Nmero de ncleos lgicos del sistema (grado de paralelismo).
Este punto es un corolario obvio de la seccin anterior. Las consultas que estn
perfectamente en paralelo se ejecutan ms rpidamente en equipos con ms ncleos
porque se puede dividir el trabajo entre ms subprocesos simultneos. La cantidad total de
aumento de velocidad depende del porcentaje del trabajo total de la consulta que se puede
paralelizar. Sin embargo, no se debe dar por supuesto que todas las consultas se ejecutarn
el doble de rpido en un equipo de ocho ncleos que en uno de cuatro. Al refinar las
consultas para lograr el rendimiento ptimo, es importante medir los resultados reales en
equipos con varios ncleos. Este punto se relaciona con el punto 1: se necesitan conjuntos
de datos mayores para aprovechar los grandes recursos informticos.
3. Nmero y tipo de operaciones.
PLINQ proporciona el operador AsOrdered para las situaciones en las que es necesario
mantener el orden de elementos de la secuencia de origen. Hay un costo asociado a la
ordenacin, pero suele ser reducido. Las operaciones GroupBy y Join tambin incurren en
sobrecarga. PLINQ rinde mejor cuando puede procesar los elementos de la coleccin de
origen en cualquier orden y pasarlos al operador siguiente en cuanto estn listos.
4. Forma de ejecucin de la consulta.
Si se guardan los resultados de una consulta llamando a ToArray o ToList, los resultados de
todos los subprocesos paralelos deben combinarse en la estructura de datos nica. Esto
implica un costo computacional inevitable. De igual modo, si se recorren los resultados con
un bucle foreach (For Each en Visual Basic), hay que serializar los resultados de los
subprocesos de trabajo en el subproceso del enumerador. Pero si solo desea realizar una
accin basada en el resultado de cada subproceso, puede utilizar el mtodo ForAll para
realizar este trabajo en varios subprocesos.
5. Tipo de opciones de combinacin.
PLINQ se puede configurar para almacenar en bfer el resultado y producirlo en fragmentos
o todo a la vez cuando el conjunto de resultados est completo, o transmitir en secuencias
los resultados individuales a medida que se van produciendo. El primer resultado disminuye
el tiempo de ejecucin total y el segundo disminuye la latencia entre los elementos
producidos. Aunque las opciones de combinacin no siempre tienen un efecto importante
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 67 de 107
en el rendimiento global de las consultas, pueden influir en el rendimiento percibido, ya que
controlan cunto tiempo debe esperar un usuario para ver los resultados.
6. Tipo de creacin de particiones.
En algunos casos, una consulta PLINQ sobre una coleccin de origen indizable puede
producir una carga de trabajo desequilibrada. Cuando suceda, tal vez logre aumentar el
rendimiento de las consultas creando un particionador personalizado.
Cundo elige PLINQ el modo secuencial
PLINQ siempre intentar ejecutar una consulta con la misma rapidez que si la consulta se ejecutara
secuencialmente. Aunque PLINQ no tenga en cuenta en el costo computacional de los delegados de
usuario ni el tamao del origen de entrada, s busca ciertas "formas" de consulta. Especficamente,
busca operadores de consulta o combinaciones de operadores que hacen que normalmente una
consulta se ejecute ms despacio en modo paralelo. Cuando encuentra esas formas, PLINQ vuelve de
forma predeterminada al modo secuencial.
Sin embargo, despus de medir el rendimiento de una consulta concreta, puede determinar que
realmente se ejecuta ms rpidamente en modo paralelo. En casos as puede utilizar la marca
ParallelExecutionMode.ForceParallelism a travs del mtodo ParallelEnumerableWithExecutionMode
para indicar a PLINQ que paralelice la consulta.
La siguiente lista describe las formas de consulta que PLINQ ejecutar de forma predeterminada en
modo secuencial:
Consultas que contienen Select, Where indizado, SelectMany indizado o una clusula
ElementAt despus de un operador de clasificacin o de filtrado que ha quitado o
reorganizado los ndices originales.
Consultas que contienen un operador Take, TakeWhile, Skip, SkipWhile y donde los ndices
de la secuencia de origen no estn en el orden original.
Consultas que contienen Zip o SequenceEquals, a menos que uno de los orgenes de datos
tenga un ndice ordenado inicialmente y el otro origen de datos sea indizable (es decir una
matriz o IList (T)).
Consultas que contienen Concat, a menos que se apliquen a orgenes de datos indizables.
Consultas que contienen Reverse, a menos que se apliquen a un origen de datos indizable.
2.3. Conservar el orden en PLINQ
En PLINQ, el objetivo es maximizar el rendimiento manteniendo la exactitud. Una consulta se debera
ejecutar lo ms rpido que fuese posible pero con resultados correctos. La exactitud exige que se
conserve el orden de la secuencia de origen en algunos casos; sin embargo, la ordenacin puede
suponer la utilizacin de muchos recursos de computacin. Por consiguiente, de forma
predeterminada, PLINQ no conserva el orden de la secuencia de origen. En este sentido, PLINQ se
parece a LINQ to SQL, pero es diferente de LINQ to Objects, que conserva el orden.
Para reemplazar el comportamiento predeterminado, puede activar la capacidad de conservar el
orden utilizando el operador AsOrdered en la secuencia de origen. Despus, puede desactivarla en la
consulta, utilizando el mtodo AsUnordered(Of TSource). Con ambos mtodos, la consulta se procesa
basado en la heurstica que determina si ejecutar la consulta como paralelo o como secuencial.
En el siguiente ejemplo se muestra una consulta paralela no ordenada que filtra todos los elementos
que coinciden con una condicin, sin intentar ordenar los resultados de forma alguna.
Dim cityQuery = From city In cities.AsParallel() Where City.Population > 10000 Take (1000)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 68 de 107
Esta consulta no obtiene necesariamente las 1000 primeras ciudades de la secuencia de origen que
cumplen la condicin, sino que algn conjunto de las 1000 ciudades que la cumplen. Los operadores
de consulta PLINQ particionan la secuencia de origen en varias secuencias secundarias que se
procesan como tareas simultneas. Si no se especifica que se conserve el orden, los resultados de
cada particin se presentan a la siguiente etapa de la consulta con un orden arbitrario. Por otra parte,
una particin puede producir un subconjunto de los resultados antes de continuar procesando los
elementos restantes. El orden resultante puede ser diferente cada vez. Una aplicacin no puede
controlar este hecho, porque depende de cmo programe los subprocesos el sistema operativo.
En el siguiente ejemplo se reemplaza el comportamiento predeterminado utilizando al operador
AsOrdered en la secuencia de origen. De esta forma se garantiza que el mtodo Take(Of TSource)
devuelve las 10 primeras ciudades de la secuencia de origen que cumplen la condicin.
Dim orderedCities = From city In cities.AsParallel().AsOrdered() Where City.Population > 10000 Take (1000)
Sin embargo, esta consulta probablemente no se ejecute tan rpido como la versin no ordenada,
porque debe realizar el seguimiento del orden original en todas las particiones y, en el momento de
la combinacin, garantizar que el orden es coherente. Por consiguiente, recomendamos usar
AsOrdered solo cuando sea estrictamente necesario y nicamente para las partes de la consulta que
lo requieran. Cuando ya no sea necesario conservar el orden, use AsUnordered(Of TSource) para
desactivarlo. En el siguiente ejemplo se consigue mediante la creacin de dos consultas.
Dim orderedCities2 = From city In cities.AsParallel().AsOrdered()
Where city.Population > 10000
Select city
Take (1000)
Dim finalResult = From city In orderedCities2.AsUnordered()
Join p In people.AsParallel() On city.Name Equals p.CityName
Select New With {.Name = city.Name, .Pop = city.Population, .Mayor = city.Mayor}
For Each city In finalResult
Console.WriteLine(city.Name & ":" & city.Pop & ":" & city.Mayor)
Next
Observe que PLINQ conserva el orden de una secuencia generada por operadores que imponen el
orden para el resto de la consulta. En otras palabras, los operadores de tipo OrderBy y ThenBy se
tratan como si fuesen seguidos de una llamada a AsOrdered.
Operadores de consulta y ordenacin
Los siguientes operadores de consulta introducen la conservacin del orden en todas las operaciones
posteriores de una consulta o hasta que se llame a AsUnordered(Of TSource):
OrderBy
OrderByDescending
ThenBy
ThenByDescending
En algunos casos, los siguientes operadores de consulta PLINQ pueden requerir secuencias de origen
ordenadas para generar resultados correctos:
Reverse(Of TSource)
SequenceEqual
TakeWhile
SkipWhile
Zip
Algunos operadores de consulta PLINQ se comportan de manera diferente, dependiendo de si su
secuencia de origen est ordenada o no. En la siguiente tabla se enumeran estos operadores.
operador ??
Resultado cuando la secuencia de
origen est ordenada
Resultado cuando la secuencia de
origen no est ordenada
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 69 de 107
Aggregate
Salida no determinista para
operaciones no asociativas o no
conmutativas.
Salida no determinista para
operaciones no asociativas o no
conmutativas.
All(Of TSource) No es aplicable No es aplicable
Any No es aplicable No es aplicable
AsEnumerable(Of
TSource)
No es aplicable No es aplicable
Average
Salida no determinista para
operaciones no asociativas o no
conmutativas.
Salida no determinista para
operaciones no asociativas o no
conmutativas.
Cast(Of TResult) Resultados ordenados Resultados no ordenados
Concat Resultados ordenados Resultados no ordenados
Count No es aplicable No es aplicable
DefaultIfEmpty No es aplicable No es aplicable
Distinct Resultados ordenados Resultados no ordenados
ElementAt(Of TSource) Se devuelve el elemento especificado Elemento arbitrario
ElementAtOrDefault(Of
Tsource)
Se devuelve el elemento especificado Elemento arbitrario
Except Resultados no ordenados Resultados no ordenados
First Se devuelve el elemento especificado Elemento arbitrario
FirstOrDefault Se devuelve el elemento especificado Elemento arbitrario
ForAll(Of TSource) Ejecucin no determinista en paralelo
Ejecucin no determinista en
paralelo
GroupBy Resultados ordenados Resultados no ordenados
GroupJoin Resultados ordenados Resultados no ordenados
Intersect Resultados ordenados Resultados no ordenados
Join Resultados ordenados Resultados no ordenados
Last Se devuelve el elemento especificado Elemento arbitrario
LastOrDefault Se devuelve el elemento especificado Elemento arbitrario
LongCount No es aplicable No es aplicable
Min No es aplicable No es aplicable
OrderBy Reordena la secuencia Inicia una nueva seccin ordenada
OrderByDescending Reordena la secuencia Inicia una nueva seccin ordenada
Range
No aplicable (mismo valor
predeterminado como AsParallel )
No es aplicable
Repeat(Of TResult)
No aplicable (mismo valor
predeterminado que AsParallel)
No es aplicable
Reverse(Of TSource) Invierte el orden No hace nada
Select Resultados ordenados Resultados no ordenados
Select (se indiza) Resultados ordenados Resultados no ordenados.
SelectMany Resultados ordenados. Resultados no ordenados
SelectMany (se indiza) Resultados ordenados. Resultados no ordenados.
SequenceEqual Comparacin ordenada Comparacin no ordenada
Single No es aplicable No es aplicable
SingleOrDefault No es aplicable No es aplicable
Skip(Of TSource) Omite los n primeros elementos Omite cualquier elemento n
SkipWhile Resultados ordenados.
No determinista. Ejecuta SkipWhile
en el orden arbitrario actual
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 70 de 107
Sum
Salida no determinista para
operaciones no asociativas o no
conmutativas.
Salida no determinista para
operaciones no asociativas o no
conmutativas.
Take(Of TSource) Toma los n primeros elementos Toma cualquier elemento n
TakeWhile Resultados ordenados
No determinista. Ejecuta TakeWhile
en el orden arbitrario actual
ThenBy Complementa OrderBy Complementa OrderBy
ThenByDescending Complementa OrderBy Complementa OrderBy
ToArray(Of TSource) Resultados ordenados Resultados no ordenados
ToDictionary No es aplicable No es aplicable
ToList(Of TSource) Resultados ordenados Resultados no ordenados
ToLookup Resultados ordenados Resultados no ordenados
Union Resultados ordenados Resultados no ordenados
Where Resultados ordenados Resultados no ordenados
Where (se indiza) Resultados ordenados Resultados no ordenados
Zip Resultados ordenados Resultados no ordenados
Activamente no se barajan los resultados no ordenados; simplemente no tienen ninguna lgica de la
clasificacin especial aplicada a ellos. En algunos casos, una consulta no ordenada puede retener la
clasificacin de la secuencia del origen. Para las consultas que utilizan al operador Select el indizado,
garantas PLINQ que los elementos de salida entrarn fuera en el orden de aumentar los ndices, pero
no realiza ninguna garanta sobre la que los ndices tendrn asignado a que los elementos.

2.4. Opciones de combinacin en PLINQ
Cuando una consulta se ejecuta en paralelo, PLINQ crea particiones en la secuencia de origen para
que varios subprocesos puedan funcionar simultneamente en diferentes partes, por lo general en
subprocesos independientes. Si los resultados se van a usar en un subproceso, por ejemplo, en un
bucle foreach (For Each en Visual Basic), los resultados de cada subproceso deben volver a
combinarse en una secuencia. El tipo de combinacin que PLINQ realiza depende de los operadores
que se encuentran en la consulta. Por ejemplo, los operadores que imponen un nuevo orden en los
resultados deben almacenar en bfer todos los elementos de todos los subprocesos. Desde el punto
de vista del subproceso utilizado (qu tambin es el del usuario de la aplicacin), una consulta
totalmente almacenada en bfer podra ejecutarse durante un perodo notable de tiempo antes de
generar su primer resultado. Otros operadores se almacenan parcialmente en bfer de forma
predeterminada; producen sus resultados en lotes. Un operador, ForAll(Of TSource)no est
almacenado en bfer de forma predeterminada. Produce inmediatamente todos los elementos de
todos los subprocesos.
Como se muestra en el siguiente ejemplo, utilizando el mtodo WithMergeOptions(Of TSource),
puede proporcionar una sugerencia a PLINQ que indica qu tipo de combinar para realizar.
Dim scanlines = From n In nums.AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered)
Where n Mod 2 = 0
Select ExpensiveFunc(n)
Si la consulta determinada no puede admitir la opcin solicitada, esta se omitir. En la mayora de los
casos, no tiene que especificar una opcin de combinacin para una consulta PLINQ. Sin embargo,
en algunos casos puede encontrar mediante pruebas y mediciones que una consulta se ejecuta mejor
en un modo no predeterminado. Un uso comn de esta opcin es forzar a un operador de
combinacin de fragmentos a transmitir en secuencias sus resultados para proporcionar una interfaz
de usuario ms sensible.
ParallelMergeOptions
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 71 de 107
La enumeracin ParallelMergeOptions incluye las siguientes opciones que especifican, para las
formas de la consulta compatibles, cmo se proporciona el resultado final de la consulta cuando se
usan los resultados en un subproceso:
Not Buffered
La opcin NotBuffered produce cada elemento procesado que se va a devolver de cada
subproceso en cuanto se genere. Este comportamiento es anlogo a la "transmisin por
secuencias" del resultado. Si el operador AsOrdered se encuentra en la consulta, NotBuffered
conserva el orden de los elementos de origen. Aunque NotBuffered empieza a proporcionar
los resultados en cuanto estn disponibles, el tiempo total para generar todos los resultados
podra ser an ms prolongado que al usar una de las otras opciones de combinacin.
Auto Buffered
La opcin AutoBuffered hace que la consulta recoja los elementos en un bfer y a
continuacin peridicamente produce de repente el contenido del bfer al subproceso para
utilizar. Esto es anlogo a proporcionar los datos de origen en "fragmentos" en lugar de usar
el comportamiento de "transmisin por secuencias" de NotBuffered. AutoBuffered puede
llevar mucho ms tiempo que NotBuffered para hacer que el primer elemento est
disponible en el subproceso utilizado. El tamao del bfer y el comportamiento productivo
exacto no se puede configurar y puede variar, en funcin de varios factores relacionados con
la consulta.
FullyBuffered
La opcin FullyBuffered produce el resultado de la consulta entera que se va a estar
almacenado en bfer antes de cualquiera de los elementos se produce. Cuando se usa esta
opcin, puede llevar mucho ms tiempo que el primer elemento est disponible en el
subproceso utilizado, pero los resultados completos se podran generar an ms
rpidamente que al usar las otras opciones.
Operadores de consulta que admiten opciones de combinacin
En la siguiente tabla se hace una lista de los operadores que admiten todos los modos de opcin de
combinacin, sujetos a las restricciones especificadas.
operador ?? Restricciones
AsEnumerable(Of TSource) Ninguno
Cast(Of TResult) Ninguno
Concat Consultas no ordenadas que solo tienen un origen de matriz o lista.
DefaultIfEmpty Ninguno
OfType(Of TResult) Ninguno
Reverse(Of TSource) Consultas no ordenadas que solo tienen un origen de matriz o lista.
Select Ninguno
SelectMany Ninguno
Skip(Of TSource) Ninguno
Take(Of TSource) Ninguno
Where Ninguno
Todos los dems operadores de consulta PLINQ podran omitir las opciones de combinacin
proporcionadas por el usuario. Algunos operadores de consulta, por ejemplo, Reverse(Of TSource) y
OrderBy, no pueden proporcionar ningn elemento hasta que todos se hayan generado y
reordenado. Por consiguiente, cuando se usa ParallelMergeOptions en una consulta que tambin
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 72 de 107
contiene a operador como Reverse(Of TSource), el comportamiento de combinacin no se aplicar
en la consulta hasta que ese operador haya generado sus resultados.
La capacidad de algunos operadores de administrar las opciones de combinacin depende del tipo
de la secuencia del origen y si el operador AsOrdered se utiliz anteriormente en la consulta.
ForAll(Of TSource) siempre es NotBuffered; produce sus elementos inmediatamente. OrderBy
siempre es FullyBuffered; debe ordenar la lista entera antes de producir.
2.5. Posibles problemas con PLINQ
En muchos casos, PLINQ puede proporcionar importantes mejoras de rendimiento con respecto a las
consultas LINQ to Objects. Sin embargo, el trabajo de paralelizar la ejecucin de las consultas aporta
una complejidad que puede conducir a problemas que, en cdigo secuencial, no son tan comunes o
que no se producen en absoluto. En este tema se indican algunas prcticas que se deben evitar al
escribir consultas PLINQ.
No se debe suponer que la ejecucin en paralelo es siempre ms rpida
En ocasiones, la paralelizacin hace que una consulta PLINQ se ejecute con mayor lentitud que su
equivalente LINQ to Objects. La regla bsica es que no es probable que las consultas con pocos
elementos de origen y delegados de usuario rpidos vayan mucho ms rpido. Sin embargo, dado
que hay muchos factores que afectan al rendimiento, se recomienda medir los resultados reales antes
de decidir si se usa PLINQ.
Evitar la escritura en ubicaciones de memoria compartidas
En cdigo secuencial, no es raro leer o escribir en variables estticas o en campos de clase. Sin
embargo, cuando varios subprocesos tienen acceso a estas variables de forma simultnea, hay
grandes posibilidades de que se produzcan condiciones de carrera. Aunque se pueden usar bloqueos
para sincronizar el acceso a la variable, el costo de la sincronizacin puede afectar negativamente al
rendimiento. Por tanto, se recomienda evitar, o al menos limitar, el acceso al estado compartido en
una consulta PLINQ en la medida de lo posible.
Evitar la paralelizacin excesiva
Si usa el operador AsParallel, incurre en costos de sobrecarga al crear particiones de la coleccin de
origen y sincronizar los subprocesos de trabajo. El nmero de procesadores del equipo reduce
tambin las ventajas de la paralelizacin. Si se ejecutan varios subprocesos enlazados a clculos en
un nico procesador, no se gana en velocidad. Por tanto, debe tener cuidado para no paralelizar en
exceso una consulta.
El escenario ms comn en el que se puede producir un exceso de paralelizacin son las consultas
anidadas, tal como se muestra en el siguiente fragmento de cdigo.
Dim q = From cust In customers.AsParallel()
From order In cust.Orders.AsParallel()
Where order.OrderDate > aDate
Select New With {cust, order}
En este caso, es mejor paralelizar nicamente el origen de datos exterior (clientes) a menos que se
cumplan una o ms de las siguientes condiciones:
Se sabe que el origen de datos interno (cust.Orders) es muy largo.
Se realiza un clculo caro en cada pedido. (La operacin que se muestra en el ejemplo no es
cara.)
Se sabe que el sistema de destino tiene suficientes procesadores como para controlar el
nmero de subprocesos que se producirn al paralelizar la consulta de cust.Orders.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 73 de 107
En todos los casos, la mejor manera de determinar la forma ptima de la consulta es mediante la
prueba y la medicin.
Evitar llamadas a mtodos que no son seguros para subprocesos
La escritura en mtodos de instancia que no son seguros para subprocesos de una consulta PLINQ
puede producir daos en los datos, que pueden pasar o no desapercibidos en el programa. Tambin
puede dar lugar a excepciones. En el siguiente ejemplo, varios subprocesos estaran intentando
llamar simultneamente al mtodo Filestream.Write, que no se admite en la clase.
Dim fs As FileStream = File.OpenWrite() a.Where(...).OrderBy(...).Select(...).ForAll(Sub(x) fs.Write(x))
Limitar las llamadas a mtodos seguros para subprocesos
La mayora de los mtodos estticos de .NET Framework son seguros para subprocesos y se les
pueden llamar simultneamente desde varios subprocesos. Sin embargo, incluso en estos casos, la
sincronizacin que esto supone puede conducir a una ralentizacin importante en la consulta.
Evitar operaciones de ordenacin innecesarias
Cuando PLINQ ejecuta una consulta en paralelo, divide la secuencia de origen en particiones con las
que se puede trabajar de forma simultnea en varios subprocesos. De forma predeterminada, el
orden en que se procesan las particiones y se entregan los resultados no es predecible (excepto para
operadores como OrderBy). Puede indicar a PLINQ que conserve la ordenacin de las secuencias de
origen, pero esto tiene un impacto negativo en el rendimiento. El procedimiento recomendado,
siempre que sea posible, consiste en estructurar las consultas de forma que no dependan del
mantenimiento del orden.
Preferir ForAll a ForEach cuando sea posible
Si bien PLINQ ejecuta una consulta en varios subprocesos, si utiliza los resultados en un bucle foreach
(For Each en Visual Basic), los resultados de la consulta se deben combinar de nuevo en un nico
subproceso y el enumerador debe tener acceso a ellos en serie. En algunos casos, esto es inevitable;
sin embargo, siempre que sea posible, utilice el mtodo ForAll para permitir que cada subproceso
genera sus propios resultados, por ejemplo, escribiendo en una coleccin segura para subprocesos
como ConcurrentBag.
El mismo problema se aplica a ForEach. En otras palabras, se debera preferir
source.AsParallel().Where(). ForAll(...) mucho antes que Parallel.ForEach(source.AsParallel().Where(), ...).
Ser consciente de los problemas de afinidad de los subprocesos
Algunas tecnologas, como la interoperabilidad COM para componentes STA (apartamento de un
nico subproceso), Windows Forms y Windows Presentation Foundation (WPF), imponen
restricciones de afinidad de subprocesos que exigen que el cdigo se ejecute en un subproceso
determinado. Por ejemplo, tanto en Windows Forms como en WPF, solo se puede tener acceso a un
control en el subproceso donde se cre. Si intenta tener acceso al estado compartido de un control
Windows Forms en una consulta PLINQ, se produce una excepcin si est ejecuta en el depurador.
(Este valor se puede desactivar.) Sin embargo, si la consulta se utiliza en el subproceso de la interfaz
de usuario, puede tener acceso al control desde el bucle foreach que enumera los resultados de la
consulta, ya que este cdigo se ejecuta en un nico subproceso.
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 tal vez
no tengan que ejecutarse en paralelo. Por consiguiente, se debe evitar escribir cdigo cuya exactitud
dependa de la ejecucin en paralelo de las iteraciones o de la ejecucin de las iteraciones en algn
orden concreto.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 74 de 107
Por ejemplo, es probable que este cdigo lleve a un interbloqueo:
Dim mre = New ManualResetEventSlim()
Enumerable.Range(0, ProcessorCount * 100).AsParallel().ForAll(Sub(j)
If j = Environment.ProcessorCount Then
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()
End If
End Sub) ' deadlocks
En este ejemplo, una iteracin establece un evento y el resto de las iteraciones esperan el evento.
Ninguna de las iteraciones que esperan puede completarse hasta que se haya completado la
iteracin del valor de evento. Sin embargo, es posible que las iteraciones que esperan bloqueen
todos los subprocesos que se utilizan para ejecutar el bucle paralelo, antes de que la iteracin del
valor de evento haya tenido oportunidad de ejecutarse. Esto produce un interbloqueo: la iteracin
del valor de evento nunca se ejecutar y las iteraciones que esperan nunca se activarn.
En concreto, una iteracin de un bucle paralelo no debe esperar nunca otra iteracin del bucle para
progresar. Si el bucle paralelo decide programar las iteraciones secuencialmente pero en el orden
contrario, se producir un interbloqueo.

2.6. Cmo: Crear y ejecutar una consulta PLINQ simple
En el siguiente ejemplo se muestra cmo crear un consulta LINQ paralela simple utilizando el mtodo
de extensin AsParallel de la secuencia de origen y cmo ejecutarla utilizando el mtodo ForAll(Of
TSource).
Ejemplo
Sub SimpleQuery()
Dim source = Enumerable.Range(100, 20000)
' Result sequence might be out of order.
Dim parallelQuery = From num In source.AsParallel()
Where num Mod 10 = 0
Select num
' Process result sequence in parallel
parallelQuery.ForAll(Sub(e)
DoSomething(e)
End Sub)
' Or use For Each to merge results first
' as in this example, Where results must
' be serialized sequentially through static Console method.
For Each n In parallelQuery
Console.Write("{0} ", n)
Next
' You can also use ToArray, ToList, etc
' as with LINQ to Objects.
Dim parallelQuery2 = (From num In source.AsParallel()
Where num Mod 10 = 0
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 75 de 107
Select num).ToArray()
'Method syntax is also supported
Dim parallelQuery3 = source.AsParallel().Where(Function(n)
Return (n Mod 10) = 0
End Function).Select(Function(n)
Return n
End Function)
For Each i As Integer In parallelQuery3
Console.Write("{0} ", i)
Next
Console.ReadLine()
End Sub
' A toy function to demonstrate syntax. Typically you need a more
' computationally expensive method to see speedup over sequential queries.
Sub DoSomething(ByVal i As Integer)
Console.Write("{0:###.## }", Math.Sqrt(i))
End Sub
En este ejemplo se muestra el modelo bsico para crear y ejecutar cualquier consulta LINQ paralela
cuando la clasificacin de la secuencia del resultado no es importante; las consultas no ordenadas
son generalmente ms rpidas que las ordenadas. La consulta crea particiones del origen en tareas
que se ejecutan de forma asincrnica en varios subprocesos. El orden en que se completa cada tarea
depende no solo de la cantidad de trabajo que se precisa para procesar los elementos de la particin,
sino tambin de factores externos, como la forma en que el sistema operativo programa cada
subproceso., Se piensa que este ejemplo muestra el uso y no se puede ejecutar ms rpidamente que
LINQ secuencial el equivalente a Objects consulte.

2.7. Cmo: Controlar la ordenacin en una consulta PLINQ
En estos ejemplos se muestra cmo controlar la clasificacin en una consulta PLINQ utilizando el
mtodo de extensin AsOrdered.
Precaucin
Se piensa principalmente que estos ejemplos muestran el uso y pueden o no se puede ejecutar ms
rpidamente que LINQ secuencial el equivalente a las consultas de Objects.
Ejemplo
En el siguiente ejemplo se mantiene el orden de la secuencia de origen. A veces esto resulta
necesario, por ejemplo, cuando algunos operadores de consulta necesitan una secuencia de origen
ordenada para generar los resultados correctos.
Sub OrderedQuery()
Dim source = Enumerable.Range(9, 10000)
' Source is ordered let's preserve it.
Dim parallelQuery = From num In source.AsParallel().AsOrdered()
Where num Mod 3 = 0
Select num
' Use For Each to preserve order at execution time.
For Each item In parallelQuery
Console.Write("{0} ", item)
Next
' Some operators expect an ordered source sequence.
Dim lowValues = parallelQuery.Take(10)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 76 de 107
End Sub
En el siguiente ejemplo se muestran algunos operadores de consulta cuya secuencia de origen es
muy probable que est ordenada. Estos operadores pueden funcionar en secuencias no ordenadas,
aunque pueden generar resultados inesperados.
' Paste into PLINQDataSample class
Shared Sub SimpleOrdering()
Dim customers As List(Of Customer) = GetCustomers().ToList()
' Take the first 20, preserving the original order
Dim firstTwentyCustomers = customers _
.AsParallel() _
.AsOrdered() _
.Take(20)
Console.WriteLine("Take the first 20 in original order")
For Each c As Customer In firstTwentyCustomers
Console.Write(c.CustomerID & " ")
Next
' All elements in reverse order.
Dim reverseOrder = customers _
.AsParallel() _
.AsOrdered() _
.Reverse()
Console.WriteLine(vbCrLf & "Take all elements in reverse order")
For Each c As Customer In reverseOrder
Console.Write("{0} ", c.CustomerID)
Next
' Get the element at a specified index.
Dim cust = customers.AsParallel() _
.AsOrdered() _
.ElementAt(48)
Console.WriteLine("Element #48 is: " & cust.CustomerID)
End Sub
Para ejecutar este mtodo, pguelo en la clase PLINQDataSample del proyecto Ejemplo de datos de
PLINQ y presione F5.
En el ejemplo siguiente se muestra cmo se mantiene el orden en la primera parte de una consulta,
cmo se quita el orden para aumentar el rendimiento de una clusula de combinacin y cmo se
aplica de nuevo el orden a la secuencia del resultado final.
' Paste into PLINQDataSample class
Sub OrderedThenUnordered()
Dim Orders As IEnumerable(Of Order) = GetOrders()
Dim orderDetails As IEnumerable(Of OrderDetail) = GetOrderDetails()
' Sometimes it's easier to create a query
' by composing two subqueries
Dim query1 = From ord In Orders.AsParallel()
Where ord.OrderDate < DateTime.Parse("07/04/1997")
Select ord
Order By ord.CustomerID
Take 20
Dim query2 = From ord In query1.AsUnordered()
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 77 de 107
Join od In orderDetails.AsParallel() On ord.OrderID Equals od.OrderID
Order By od.ProductID
Select New With {ord.OrderID, ord.CustomerID, od.ProductID}
For Each item In query2
Console.WriteLine("{0} {1} {2}", item.OrderID, item.CustomerID, item.ProductID)
Next
End Sub
Para ejecutar este mtodo, pguelo en la clase PLINQDataSample del proyecto Ejemplo de datos de
PLINQ y presione F5.

2.8. Cmo: Combinar consultas LINQ paralelas y secuenciales
En este ejemplo se muestra cmo utilizar el mtodo AsSequential(Of TSource) para indicarle a PLINQ
que procesar secuencialmente a todos los operadores subsiguientes en la consulta. Aunque el
procesamiento secuencial es por lo general ms lento que el procesamiento en paralelo, a veces es
necesario para generar resultados correctos.
Ejemplo
En el siguiente ejemplo se muestra un escenario en el que se requiere AsSequential(Of TSource), a
saber, para conservar el orden que se estableci en una clusula anterior de la consulta.
' Paste into PLINQDataSample class
Shared Sub SequentialDemo()
Dim orders = GetOrders()
Dim query = From ord In orders.AsParallel()
Order By ord.CustomerID
Select New With
{
ord.OrderID,
ord.OrderDate,
ord.ShippedDate
}
Dim query2 = query.AsSequential().Take(5)
For Each item In query2
Console.WriteLine("{0}, {1}, {2}", item.OrderDate, item.OrderID, item.ShippedDate)
Next
End Sub

2.9. Cmo: Controlar excepciones en una consulta PLINQ
En el primer ejemplo de este tema se muestra cmo controlar la excepcin
System.AggregateException que se puede iniciar desde una consulta PLINQ cuando se ejecuta. En el
segundo ejemplo se muestra cmo incluir bloques try-catch dentro de los delegados, lo ms
prximos posibles al punto donde se iniciar la excepcin. De esta manera, se pueden detectar en
cuanto se producen y, posiblemente, continuar con la ejecucin de la consulta. Cuando las
excepciones pueden propagarse de nuevo al subproceso de unin, es posible que una consulta
contine procesando algunos elementos despus de que se haya producido la excepcin.
En algunos casos, cuando PLINQ utiliza la ejecucin secuencial y se produce una excepcin, es
posible que esta se propague directamente y no se encapsule en una excepcin AggregateException.
Adems, las excepciones ThreadAbortException siempre se propagan directamente.
Ejemplo
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 78 de 107
En este ejemplo se muestra cmo colocar los bloques try-catch alrededor del cdigo que ejecuta la
consulta para detectar las excepciones System.AggregateExceptions que se produzcan.
' Paste into PLINQDataSample class
Shared Sub PLINQExceptions_1()
' Using the raw string array here. See PLINQ Data Sample.
Dim customers As String() = GetCustomersAsStrings().ToArray()
' First, we must simulate some currupt input.
customers(20) = "###"
'throws indexoutofrange
Dim query = From cust In customers.AsParallel() _
Let fields = cust.Split(","c) _
Where fields(3).StartsWith("C") _
Select fields
Try
' We use ForAll although it doesn't really improve performance
' since all output is serialized through the Console.
query.ForAll(Sub(e)
Console.WriteLine("City: {0}, Thread:{1}")
End Sub)
Catch e As AggregateException
' In this design, we stop query processing when the exception occurs.
For Each ex In e.InnerExceptions
Console.WriteLine(ex.Message)
If TypeOf ex Is IndexOutOfRangeException Then
Console.WriteLine("The data source is corrupt. Query stopped.")
End If
Next
End Try
End Sub
En este ejemplo, la consulta no puede continuar una vez producida la excepcin. Cuando el cdigo
de aplicacin detecta la excepcin, PLINQ ya ha detenido la consulta en todos los subprocesos.
En el siguiente ejemplo se muestra cmo colocar un bloque try-catch en un delegado para permitir
que se detecte una excepcin y que contine la ejecucin de la consulta.
' Paste into PLINQDataSample class
Shared Sub PLINQExceptions_2()
Dim customers() = GetCustomersAsStrings().ToArray()
' Using the raw string array here.
' First, we must simulate some currupt input
customers(20) = "###"
' Create a delegate with a lambda expression.
' Assume that in this app, we expect malformed data
' occasionally and by design we just report it and continue.
Dim isTrue As Func(Of String(), String, Boolean) = Function(f, c)
Try
Dim s As String = f(3)
Return s.StartsWith(c)
Catch e As IndexOutOfRangeException
Console.WriteLine("Malformed cust: {0}", f)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 79 de 107
Return False
End Try
End Function
' Using the raw string array here
Dim query = From cust In customers.AsParallel()
Let fields = cust.Split(","c)
Where isTrue(fields, "C")
Select New With {.City = fields(3)}
Try
' We use ForAll although it doesn't really improve performance
' since all output must be serialized through the Console.
query.ForAll(Sub(e) Console.WriteLine(e.city))
' IndexOutOfRangeException will not bubble up
' because we handle it where it is thrown.
Catch e As AggregateException
For Each ex In e.InnerExceptions
Console.WriteLine(ex.Message)
Next
End Try
End

2.10. Cmo: Cancelar una consulta PLINQ
En los siguientes ejemplos se muestran dos maneras de cancelar una consulta PLINQ. En el primer
ejemplo se muestra cmo cancelar una consulta que consta principalmente de recorridos de datos.
En el segundo ejemplo se muestra cmo cancelar una consulta que contiene una funcin de usuario
que resulta cara en los clculos.
Ejemplo
Class Program
Private Shared Sub Main(ByVal args As String())
Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
Dim cs As New CancellationTokenSource()
' Start a new asynchronous task that will cancel the
' operation from another thread. Typically you would call
' Cancel() in response to a button click or some other
' user interface event.
Task.Factory.StartNew(Sub()
UserClicksTheCancelButton(cs)
End Sub)
Dim results As Integer() = Nothing
Try
results = (From num In source.AsParallel().WithCancellation(cs.Token) _
Where num Mod 3 = 0 _
Order By num Descending _
Select num).ToArray()
Catch e As OperationCanceledException
Console.WriteLine(e.Message)
Catch ae As AggregateException
If ae.InnerExceptions IsNot Nothing Then
For Each e As Exception In ae.InnerExceptions
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 80 de 107
Console.WriteLine(e.Message)
Next
End If
End Try
If results IsNot Nothing Then
For Each item In results
Console.WriteLine(item)
Next
End If
Console.WriteLine()
Console.ReadKey()
End Sub
Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)
' Wait between 150 and 500 ms, then cancel.
' Adjust these values if necessary to make
' cancellation fire while query is still executing.
Dim rand As New Random()
Thread.Sleep(rand.[Next](150, 350))
cs.Cancel()
End Sub
End Class
El marco de PLINQ no incluye una nica excepcin OperationCanceledException en
System.AggregateException; OperationCanceledException se debe controlar en un bloque catch
independiente. Si uno o ms delegados de usuario inician una excepcin
OperationCanceledException(externalCT) (mediante una estructura
System.Threading.CancellationToken externa) pero ninguna otra excepcin, y la consulta se defini
como AsParallel().WithCancellation(externalCT), PLINQ emitir una nica excepcin
OperationCanceledException (externalCT) en lugar de System.AggregateException. Sin embargo, si
un delegado de usuario inicia una excepcin OperationCanceledException y otro delegado inicia otro
tipo de excepcin, ambas excepciones se incluyen en AggregateException.
A continuacin se proporciona una orientacin general sobre la cancelacin:
1. Si realiza una cancelacin del delegado de usuario, debera informar a PLINQ sobre la
estructura CancellationToken externa e iniciar una excepcin OperationCanceledException
(externalCT).
2. Si se produce la cancelacin y no se inicia ninguna otra excepcin, debera controlar una
clase OperationCanceledException en lugar de AggregateException.
En el siguiente ejemplo se muestra cmo controlar la cancelacin cuando se cuenta con una funcin
cara en clculos en el cdigo de usuario.
Class Program2
Private Shared Sub Main(ByVal args As String())
Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
Dim cs As New CancellationTokenSource()
' Start a new asynchronous task that will cancel the operation from another thread. Typically you would call
' Cancel() in response to a button click or some other user interface event.
Task.Factory.StartNew(Sub()
UserClicksTheCancelButton(cs)
End Sub)
Dim results As Double() = Nothing
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 81 de 107
Try
results = (From num In source.AsParallel().WithCancellation(cs.Token) _
Where num Mod 3 = 0 _
Select [Function](num, cs.Token)).ToArray()
Catch e As OperationCanceledException
Console.WriteLine(e.Message)
Catch ae As AggregateException
If ae.InnerExceptions IsNot Nothing Then
For Each e As Exception In ae.InnerExceptions
Console.WriteLine(e.Message)
Next
End If
End Try
If results IsNot Nothing Then
For Each item In results
Console.WriteLine(item)
Next
End If
Console.WriteLine()
Console.ReadKey()
End Sub
' A toy method to simulate work.
Private Shared Function [Function](ByVal n As Integer, ByVal ct As CancellationToken) As Double
' If work is expected to take longer than 1 ms then try to check cancellation status more
' often within that work.
For i As Integer = 0 To 4
' Work hard for approx 1 millisecond.
Thread.SpinWait(50000)
' Check for cancellation request.
If ct.IsCancellationRequested Then
Throw New OperationCanceledException(ct)
End If
Next
' Anything will do for our purposes.
Return Math.Sqrt(n)
End Function
Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)
' Wait between 150 and 500 ms, then cancel.
' Adjust these values if necessary to make cancellation fire while query is still executing.
Dim rand As New Random()
Thread.Sleep(rand.[Next](150, 350))
Console.WriteLine("Press 'c' to cancel")
If Console.ReadKey().KeyChar = "c"c Then
cs.Cancel()
End If
End Sub
End Class
Al administrar la cancelacin en cdigo de usuario, no tiene que utilizar WithCancellation(Of TSource)
en la definicin de la consulta. Sin embargo, recomendamos hacer esto porque WithCancellation(Of
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 82 de 107
TSource) no tiene ningn efecto en rendimiento de las consultas y permite administrar la cancelacin
por operadores de consulta y su cdigo de usuario.
Para garantizar la capacidad de respuesta del sistema, se recomienda comprobar la cancelacin
alrededor de una vez por milisegundo; tambin se considera aceptable, sin embargo, un perodo
mximo de 10 milisegundos. Esta frecuencia no tiene por qu tener un impacto negativo en el
rendimiento del cdigo.
Cuando se dispone un enumerador, por ejemplo cuando el cdigo se escapa de un bucle de foreach
(Para Cada uno en Visual Basic) que est iterando sobre los resultados de la consulta, a continuacin,
la consulta est cancelada, pero no se produce ninguna excepcin.

2.11. Cmo: Escribir una funcin de agregado personalizada de PLINQ
En este ejemplo se muestra cmo utilizar el mtodo Aggregate para aplicar una funcin de
agregacin personalizada a una secuencia del origen.
Ejemplo
En el siguiente ejemplo se calcula la desviacin estndar de una secuencia de enteros.
Class aggregation
Private Shared Sub Main(ByVal args As String())
' Create a data source for demonstration purposes.
Dim source As Integer() = New Integer(99999) {}
Dim rand As New Random()
For x As Integer = 0 To source.Length - 1
' Should result in a mean of approximately 15.0.
source(x) = rand.[Next](10, 20)
Next
' Standard deviation calculation requires that we first calculate the mean average. Average is a predefined
' aggregation operator, along with Max, Min and Count.
Dim mean As Double = source.AsParallel().Average()
' We use the overload that is unique to ParallelEnumerable. The third Func parameter combines the results from
each
' thread. initialize subtotal. Use decimal point to tell the compiler this is a type double. Can also use: 0d.
' do this on each thread aggregate results after all threads are done.
' perform standard deviation calc on the aggregated result.
Dim standardDev As Double = source.AsParallel().Aggregate(0.0R, Function(subtotal, item) subtotal +
Math.Pow((item - mean), 2), Function(total, thisThread) total + thisThread, Function(finalSum) Math.Sqrt((finalSum /
(source.Length - 1))))
Console.WriteLine("Mean value is = {0}", mean)
Console.WriteLine("Standard deviation is {0}", standardDev)
Console.ReadLine()
End Sub
End Class
En este ejemplo se utiliza una sobrecarga del operador de consulta estndar Aggregate, que solo
existe en PLINQ. Esta sobrecarga toma un delegado System.Func(Of T1, T2, TResult) adicional como
tercer parmetro de entrada. Este delegado combina los resultados de todos los subprocesos antes
de realizar el clculo final en los resultados agregados. En este ejemplo sumamos las sumas de todos
los subprocesos.
Tenga en cuenta que cuando el cuerpo de una expresin lambda est compuesto por una nica
expresin, el valor devuelto del delegado System.Func(Of T, TResult) es el valor de la expresin.

2.12. Cmo: Especificar el modo de ejecucin en PLINQ
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 83 de 107
En este ejemplo se muestra cmo forzar a PLINQ a omitir su heurstica predeterminada y ejecutar en
paralelo una consulta sin tener en cuenta la forma de la consulta.
Ejemplo
Private Shared Sub ForceParallel()
Dim customers = GetCustomers()
Dim parallelQuery = (From cust In
customers.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism) _
Where cust.City = "Berlin" _
Select cust.CustomerName).ToList()
End Sub
PLINQ est diseado para aprovechar las oportunidades para la ejecucin en paralelo. Sin embargo,
no todas las consultas se benefician de la ejecucin en paralelo. Por ejemplo, cuando una consulta
contiene un delegado de usuario nico que hace muy poco trabajo, la consulta normalmente se
ejecutar ms rpidamente de forma secuencial. Esto se debe a que la sobrecarga necesaria para
habilitar la ejecucin en paralelo es ms costosa que la velocidad que se obtiene. Por consiguiente,
PLINQ no ejecuta en paralelo cada consulta de forma automtica. Primero examina la forma de la
consulta y los diversos operadores que la comprenden. En funcin de este anlisis, PLINQ en el modo
de ejecucin predeterminado puede decidir ejecutar algunas consultas o todas ellas
secuencialmente. Sin embargo, en algunos casos puede saber sobre su consulta ms de lo que PLINQ
puede determinar a partir de su anlisis. Por ejemplo, puede saber que un delegado es muy costoso
y que la consulta se beneficiar definitivamente de la ejecucin en paralelo. En casos como ste,
puede utilizar el mtodo WithExecutionMode(Of TSource) y especificar el valor ForceParallelism para
indicarle a PLINQ que ejecutar siempre la consulta como paralelo.

2.13. Cmo: Especificar opciones de combinacin en PLINQ
En este ejemplo se muestra cmo especificar las opciones de combinacin que se aplicarn a todos
los operadores subsiguientes en una consulta PLINQ. No tiene que establecer las opciones de
combinacin explcitamente, pero, si lo hace, puede mejorar rendimiento.
Ejemplo
En el siguiente ejemplo se muestra el comportamiento de las opciones de combinacin en un
escenario bsico que tiene un origen no ordenado y aplica una funcin que utiliza muchos recursos a
cada elemento.
Class MergeOptions2
Sub DoMergeOptions()
Dim nums = Enumerable.Range(1, 10000)
' Replace NotBuffered with AutoBuffered
' or FullyBuffered to compare behavior.
Dim scanLines = From n In nums.AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered)
Where n Mod 2 = 0
Select ExpensiveFunc(n)
Dim sw = System.Diagnostics.Stopwatch.StartNew()
For Each line In scanLines
Console.WriteLine(line)
Next
Console.WriteLine("Elapsed time: {0} ms. Press any key to exit.")
Console.ReadKey()
End Sub
' A function that demonstrates what a fly sees when it watches television :-)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 84 de 107
Function ExpensiveFunc(ByVal i As Integer) As String
System.Threading.Thread.SpinWait(2000000)
Return String.Format("{0} *****************************************", i)
End Function
End Class
En casos donde la opcin AutoBuffered incurre en una latencia indeseable antes de que se produzca
el primer elemento, pruebe la opcin NotBuffered para producir los elementos de resultado ms
rpido y ms fcilmente.

2.14. Cmo: Recorrer en iteracin directorios con PLINQ
En este ejemplo se muestran dos maneras simples de paralelizar las operaciones en directorios de
archivo. La primera consulta utiliza el mtodo GetFiles para rellenar una matriz de nombres de
archivo en un directorio y todos los subdirectorios. Este mtodo no devuelve hasta que se rellene la
matriz completa, y por consiguiente puede introducir al principio la latencia de la operacin. Sin
embargo, una vez rellenada la matriz, PLINQ puede procesarlo muy rpidamente en paralelo.
Los segundos usos de la consulta en los mtodos EnumerateFiles y los EnumerateDirectories
estticos que empiezan a devolver los resultados inmediatamente. Este enfoque puede ser ms
rpido cuando est iterando sobre los rboles de directorios grandes, aunque el tiempo de proceso
compar al primer ejemplo puede depender de muchos factores.
Ejemplo
El siguiente ejemplo muestra cmo iterar sobre los directorios de archivo en escenarios simples al
tener el acceso a todos los directorios en el rbol, los tamaos de archivo no son muy grandes y los
tiempos de acceso no son significativos. Este enfoque implica un perodo de latencia al principio
mientras se construye la matriz de nombres de archivo.
El siguiente ejemplo muestra cmo iterar sobre los directorios de archivo en escenarios simples al
tener el acceso a todos los directorios en el rbol, los tamaos de archivo no son muy grandes y los
tiempos de acceso no son significativos. Este enfoque empieza a generar resulta ms rpidamente
que el ejemplo anterior.
Este idioma no es compatible o no hay ningn ejemplo de cdigo disponible.
Al utilizar GetFiles, asegurarse de tener los permisos necesarios en todos los directorios en el rbol.
De lo contrario se producir una excepcin y no se devolver ningn resultado. Al utilizar
EnumerateDirectories en una consulta PLINQ, es problemtico para administrar las excepciones I/O
de una manera elegante que le permite seguir iterando. Si su cdigo debe administrar excepciones
de acceso no autorizado o E/S, a continuacin, debera considerar el enfoque descrito en Cmo:
Recorrer en iteracin directorios con la clase paralela.
If I/O latency is an issue, for example with file I/O over a network, consider using one of the
asynchronous I/O techniques described in TPL y la programacin asincrnica tradicional de .NET.

2.15. Cmo: Medir el rendimiento de consultas PLINQ
En este ejemplo se muestra cmo usar la clase Stopwatch para medir el tiempo que tarda en
ejecutarse una consulta PLINQ.
Ejemplo
En este ejemplo se usa un bucle foreach vaco (For Each en Visual Basic) para medir el tiempo que
tarda en ejecutarse la consulta. En el cdigo real, normalmente, el bucle contiene pasos de
procesamiento adicionales que aumentan el tiempo de ejecucin total de la consulta. Observe que el
cronmetro se inicia justo antes del bucle, porque es en ese momento cuando comienza la ejecucin
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 85 de 107
de la consulta. Si necesita una medicin ms exacta, puede usar la propiedad ElapsedTicks en lugar
de ElapsedMilliseconds.
Sub Main()
Dim source = Enumerable.Range(0, 3000000)
Dim queryToMeasure = From num In source
Where num Mod 3 = 0
Select Math.Sqrt(num)
Console.WriteLine("Measuring...")
' The query does not run until it is enumerated.
' Therefore, start the timer here.
Dim sw = System.Diagnostics.Stopwatch.StartNew()
' For pure query cost, enumerate and do nothing else.
For Each n As Double In queryToMeasure
Next
Dim elapsed As Long
elapsed = sw.ElapsedMilliseconds ' or sw.ElapsedTicks
Console.WriteLine("Total query time: {0} ms.", elapsed)
' Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
El tiempo de ejecucin total es una medida til cuando se prueban implementaciones de consultas,
pero no siempre lo dice todo. Para obtener una vista ms detallada y completa de la interaccin de
los subprocesos de consulta entre s y con otros procesos en ejecucin, use el visualizador de
simultaneidad. Esta herramienta est disponible en Microsoft Visual Studio 2010 Premium.

2.16. Ejemplo de datos de PLINQ
Este ejemplo contiene datos de ejemplo en formato .csv, junto con mtodos que lo transforman en
colecciones en memoria de clientes, productos, pedidos y detalles de pedidos. Si desea realizar ms
pruebas con PLINQ, puede pegar ejemplos de cdigo de otros temas en el cdigo de este tema e
invocarlo desde el mtodo Main. Tambin puede usar estos datos con sus propias consultas PLINQ.
Los datos representan un subconjunto de la base de datos Northwind. Se incluyen cincuenta (50)
registros de cliente, pero no todos los campos. Se incluye un subconjunto de las filas de pedidos y
los detalles correspondientes de Order_Detail de cada cliente. Se incluyen todos los productos.
Para configurar este ejemplo
1. Cree un proyecto de aplicacin de consola de Visual Basic o Visual C#.
2. Reemplace el contenido de Module1.vb o Program.cs utilizando el cdigo que se incluye
despus de estos pasos.
3. En el men Proyecto, haga clic en Agregar nuevo elemento. Seleccione Archivo de texto
y, a continuacin, haga clic en Aceptar. Copie los datos de este tema y, a continuacin,
pguelos en el nuevo archivo de texto. En el men Archivo, haga clic en Guardar, asigne al
archivo el nombre Plinqdata.csv y, a continuacin, gurdelo en la carpeta que contiene sus
archivos de cdigo fuente.
4. Presione F5 para comprobar que el proyecto se compila y se ejecuta correctamente. Se
debera mostrar la salida siguiente en la ventana de la consola.
Customer count: 50
Product count: 77
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 86 de 107
Order count: 190
Order Details count: 483
Press any key to exit.
' This class contains a subset of data from the Northwind database
' in the form of string arrays. The methods such as GetCustomers, GetOrders, and so on
' transform the strings into object arrays that you can query in an object-oriented way.
' Many of the code examples in the PLINQ How-to topics are designed to be pasted into
' the PLINQDataSample class and invoked from the Main method.
' IMPORTANT: This data set is not large enough for meaningful comparisons of PLINQ vs. LINQ performance.
Class PLINQDataSample
Shared Sub Main(ByVal args As String())
'Call methods here.
TestDataSource()
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Shared Sub TestDataSource()
Console.WriteLine("Customer count: {0}", GetCustomers().Count())
Console.WriteLine("Product count: {0}", GetProducts().Count())
Console.WriteLine("Order count: {0}", GetOrders().Count())
Console.WriteLine("Order Details count: {0}", GetOrderDetails().Count())
End Sub
#Region "DataClasses"
Class Order
Public Sub New()
_OrderDetails = New Lazy(Of OrderDetail())(Function() GetOrderDetailsForOrder(OrderID))
End Sub
Private _OrderID As Integer
Public Property OrderID() As Integer
Get
Return _OrderID
End Get
Set(ByVal value As Integer)
_OrderID = value
End Set
End Property
Private _CustomerID As String
Public Property CustomerID() As String
Get
Return _CustomerID
End Get
Set(ByVal value As String)
_CustomerID = value
End Set
End Property
Private _OrderDate As DateTime
Public Property OrderDate() As DateTime
Get
Return _OrderDate
End Get
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 87 de 107
Set(ByVal value As DateTime)
_OrderDate = value
End Set
End Property
Private _ShippedDate As DateTime
Public Property ShippedDate() As DateTime
Get
Return _ShippedDate
End Get
Set(ByVal value As DateTime)
_ShippedDate = value
End Set
End Property
Private _OrderDetails As Lazy(Of OrderDetail())
Public ReadOnly Property OrderDetails As OrderDetail()
Get
Return _OrderDetails.Value
End Get
End Property
End Class
Class Customer
Private _Orders As Lazy(Of Order())
Public Sub New()
_Orders = New Lazy(Of Order())(Function() GetOrdersForCustomer(_CustomerID))
End Sub
Private _CustomerID As String
Public Property CustomerID() As String
Get
Return _CustomerID
End Get
Set(ByVal value As String)
_CustomerID = value
End Set
End Property
Private _CustomerName As String
Public Property CustomerName() As String
Get
Return _CustomerName
End Get
Set(ByVal value As String)
_CustomerName = value
End Set
End Property
Private _Address As String
Public Property Address() As String
Get
Return _Address
End Get
Set(ByVal value As String)
_Address = value
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 88 de 107
End Set
End Property
Private _City As String
Public Property City() As String
Get
Return _City
End Get
Set(ByVal value As String)
_City = value
End Set
End Property
Private _PostalCode As String
Public Property PostalCode() As String
Get
Return _PostalCode
End Get
Set(ByVal value As String)
_PostalCode = value
End Set
End Property
Public ReadOnly Property Orders() As Order()
Get
Return _Orders.Value
End Get
End Property
End Class
Class Product
Private _ProductName As String
Public Property ProductName() As String
Get
Return _ProductName
End Get
Set(ByVal value As String)
_ProductName = value
End Set
End Property
Private _ProductID As Integer
Public Property ProductID() As Integer
Get
Return _ProductID
End Get
Set(ByVal value As Integer)
_ProductID = value
End Set
End Property
Private _UnitPrice As Double
Public Property UnitPrice() As Double
Get
Return _UnitPrice
End Get
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 89 de 107
Set(ByVal value As Double)
_UnitPrice = value
End Set
End Property
End Class
Class OrderDetail
Private _OrderID As Integer
Public Property OrderID() As Integer
Get
Return _OrderID
End Get
Set(ByVal value As Integer)
_OrderID = value
End Set
End Property
Private _ProductID As Integer
Public Property ProductID() As Integer
Get
Return _ProductID
End Get
Set(ByVal value As Integer)
_ProductID = value
End Set
End Property
Private _UnitPrice As Double
Public Property UnitPrice() As Double
Get
Return _UnitPrice
End Get
Set(ByVal value As Double)
_UnitPrice = value
End Set
End Property
Private _Quantity As Double
Public Property Quantity() As Double
Get
Return _Quantity
End Get
Set(ByVal value As Double)
_Quantity = value
End Set
End Property
Private _Discount As Double
Public Property Discount() As Double
Get
Return _Discount
End Get
Set(ByVal value As Double)
_Discount = value
End Set
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 90 de 107
End Property
End Class
#End Region
Shared Function GetCustomersAsStrings() As IEnumerable(Of String)
Return System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("CUSTOMERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END CUSTOMERS") = False)
End Function
Shared Function GetCustomers() As IEnumerable(Of Customer)
Dim customers = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("CUSTOMERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END CUSTOMERS") = False)
Return From line In customers
Let fields = line.Split(","c)
Select New Customer With {
.CustomerID = fields(0).Trim(),
.CustomerName = fields(1).Trim(),
.Address = fields(2).Trim(),
.City = fields(3).Trim(),
.PostalCode = fields(4).Trim()}
End Function
Shared Function GetOrders() As IEnumerable(Of Order)
Dim orders = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("ORDERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDERS") = False)
Return From line In orders
Let fields = line.Split(","c)
Select New Order With {
.OrderID = CType(fields(0).Trim(), Integer),
.CustomerID = fields(1).Trim(),
.OrderDate = DateTime.Parse(fields(2)),
.ShippedDate = DateTime.Parse(fields(3))}
End Function
Shared Function GetOrdersForCustomer(ByVal id As String) As Order()
Dim orders = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("ORDERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDERS") = False)
Dim orderStrings = From line In orders
Let fields = line.Split(","c)
Let custID = fields(1).Trim()
Where custID = id
Select New Order With {
.OrderID = CType(fields(0).Trim(), Integer),
.CustomerID = custID,
.OrderDate = DateTime.Parse(fields(2)),
.ShippedDate = DateTime.Parse(fields(3))}
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 91 de 107
Return orderStrings.ToArray()
End Function
Shared Function GetProducts() As IEnumerable(Of Product)
Dim products = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("PRODUCTS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END PRODUCTS") = False)
Return From line In products
Let fields = line.Split(","c)
Select New Product With {
.ProductID = CType(fields(0), Integer),
.ProductName = fields(1).Trim(),
.UnitPrice = CType(fields(2), Double)}
End Function
Shared Function GetOrderDetailsForOrder(ByVal orderID As Integer) As OrderDetail()
Dim orderDetails = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("ORDER DETAILS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDER DETAILS") = False)
Dim ordDetailStrings = From line In orderDetails
Let fields = line.Split(","c)
Let ordID = Convert.ToInt32(fields(0))
Where ordID = orderID
Select New OrderDetail With {
.OrderID = ordID,
.ProductID = CType(fields(1), Integer),
.UnitPrice = CType(fields(2), Double),
.Quantity = CType(fields(3), Double),
.Discount = CType(fields(4), Double)}
Return ordDetailStrings.ToArray()
End Function
Shared Function GetOrderDetails() As IEnumerable(Of OrderDetail)
Dim orderDetails = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("ORDER DETAILS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDER DETAILS") = False)
Return From line In orderDetails
Let fields = line.Split(","c)
Select New OrderDetail With {
.OrderID = CType(fields(0), Integer),
.ProductID = CType(fields(1), Integer),
.UnitPrice = CType(fields(2), Double),
.Quantity = CType(fields(3), Double),
.Discount = CType(fields(4), Double)}
End Function
End Class

3. Estructuras de datos para la programacin paralela
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 92 de 107
.NET Framework versin 4 incorpora varios tipos nuevos que resultan tiles para la programacin en
paralelo, entre los que se incluye un conjunto de clases de coleccin simultneas, primitivas de
sincronizacin ligeras y tipos de inicializacin diferida. Puede usar estos tipos con cualquier cdigo
de aplicacin multiproceso, incluso con la biblioteca TPL (Task Parallel Library, biblioteca de
procesamiento paralelo basado en tareas) y PLINQ.
Clases de coleccin simultneas
Las clases de coleccin del espacio de nombres System.Collections.Concurrent proporcionan
operaciones Add y Remove seguras para subprocesos que evitan los bloqueos en la medida de lo
posible y, cuando es necesario, usan bloqueos especficos. A diferencia de las colecciones que se
incorporaron en las versiones 1.0 y 2.0 de .NET Framework, una clase de coleccin simultnea no
requiere que el cdigo de usuario tome ningn bloqueo cuando obtiene acceso a los elementos. Las
clases de coleccin simultneas pueden mejorar significativamente el rendimiento frente a tipos
como System.Collections.ArrayList y System.Collections.Generic.List(Of T) (con bloqueo
implementado por el usuario) en escenarios en lo que varios subprocesos agregan y quitan
elementos de una coleccin.
En la tabla siguiente se muestran las nuevas clases de coleccin simultneas:
Escriba Descripcin
System.Collections.Concurrent.BlockingCollection(Of T)
Proporciona capacidades de bloqueo y establecimiento de
lmites en colecciones seguras para subprocesos que
implementan
System.Collections.Concurrent.IProducerConsumerCollection(Of
T). Los subprocesos de productor se bloquean si no hay ranuras
disponibles o si la coleccin est completa. Los subprocesos de
consumidor se bloquean si la coleccin est vaca. Este tipo
tambin permite el acceso sin bloqueo de los subprocesos de
consumidor y productor. BlockingCollection(Of T) puede usarse
como clase base o dispositivo de copia de seguridad para
proporcionar el bloqueo y el establecimiento de lmites de
cualquier clase de coleccin que admita IEnumerable(Of T).
System.Collections.Concurrent.ConcurrentBag(Of T)
Implementacin de un contenedor seguro para subprocesos
que proporciona operaciones Add y Get escalables.
System.Collections.Concurrent.ConcurrentDictionary(Of
TKey, TValue)
Tipo de diccionario simultneo y escalable.
System.Collections.Concurrent.ConcurrentQueue(Of T) Cola FIFO simultnea y escalable.
System.Collections.Concurrent.ConcurrentStack(Of T) Pila LIFO simultnea y escalable.
Primitivas de sincronizacin
Las nuevas primitivas de sincronizacin del espacio de nombres System.Threading proporcionan una
simultaneidad especfica y un mayor rendimiento ya que evitan los costosos mecanismos de bloqueo
que se encuentran en el cdigo multithreading heredado. Algunos de los nuevos tipos, como
System.Threading.Barrier y System.Threading.CountdownEvent, no tienen equivalente en versiones
anteriores de .NET Framework.
En la tabla siguiente, se muestran los nuevos tipos de sincronizacin:
Escriba Descripcin
System.Threading.Barrier
Permite que varios subprocesos trabajen en un algoritmo
en paralelo proporcionando un punto en el que cada tarea
puede sealar su llegada y bloquearse hasta que algunas o
todas las tareas hayan llegado.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 93 de 107
System.Threading.CountdownEvent
Simplifica los escenarios de bifurcacin y unin
proporcionando un mecanismo de encuentro sencillo.
System.Threading.ManualResetEventSlim
Primitiva de sincronizacin similar a
System.Threading.Manual ResetEvent.
ManualResetEventSlim es un objeto ligero, pero solo puede
usarse en la comunicacin que tiene lugar dentro de un
proceso.
System.Threading.SemaphoreSlim
Primitiva de sincronizacin que limita el nmero de
subprocesos que pueden obtener acceso a la vez a un
recurso o grupo de recursos.
System.Threading.SpinLock
Primitiva de bloqueo de exclusin mutua que hace que el
subproceso que est intentando adquirir el bloqueo espere
en un bucle o ciclo durante un perodo de tiempo antes de
que se produzca su cuanto. En escenarios en los que se
prev que la espera del bloqueo ser breve, SpinLock
proporciona mayor rendimiento que otras formas de
bloqueo.
System.Threading.SpinWait
Tipo pequeo y ligero que iterar en ciclos durante un
perodo especificado y situar el subproceso en estado de
espera si el recuento de ciclos se supera.
Cmo: Utilizar SpinLock para la sincronizacin de bajo nivel
Cmo: Sincronizar operaciones simultneas con una clase Barrier.
Clases de inicializacin diferida
Con la inicializacin diferida, la memoria de un objeto no se asigna hasta que es necesario. La
inicializacin diferida puede mejorar el rendimiento al extender las asignaciones de objetos
uniformemente a lo largo de la duracin de un programa. Puede habilitar la inicializacin diferida en
cualquier tipo personalizado encapsulando el tipo Lazy(Of T).
En la tabla siguiente, se muestran los tipos de inicializacin diferida:
Escriba Descripcin
System.Lazy(Of T)
Proporciona una inicializacin diferida ligera y segura para
subprocesos.
System.Threading.ThreadLocal(Of
T)
Proporciona un valor de inicializacin diferida por cada
subproceso, donde cada subproceso invoca de forma diferida la
funcin de inicializacin.
System.Threading.LazyInitializer
Proporciona mtodos estticos que evitan tener que asignar una
instancia de inicializacin diferida dedicada. En su lugar, usan
referencias para garantizar que los destinos se han inicializado a
medida que se va obteniendo acceso a ellos.
Excepciones agregadas
El tipo System.AggregateException se puede utilizar para capturar varias excepciones que se
producen simultneamente en diferentes subprocesos y devolverlas al subproceso de unin como
una sola excepcin. Los tipos System.Threading.Tasks.Task y System.Threading.Tasks.Parallel as como
la PLINQ usan AggregateException en gran medida con este propsito.

4. Herramientas de diagnstico paralelo
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 94 de 107
Microsoft Visual Studio 2010 proporciona amplia compatibilidad para depurar y generar los perfiles
de aplicaciones multithreading.
Depuracin
El depurador de Visual Studio agrega nuevas ventanas para depurar aplicaciones paralelas.
Generacin de perfiles
En las vistas de informe del visualizador de simultaneidad se puede ver cmo los subprocesos de un
programa paralelo interactan entre s y con los subprocesos de otros procesos del sistema.
5. Particionadores personalizados para PLINQ y TPL
Para paralelizar una operacin en un origen de datos, uno de los pasos esenciales es crear particiones
del origen en varias secciones a las que se tenga acceso simultneamente a travs de varios
subprocesos. PLINQ y Task Parallel Library (TPL) proporcionan particionadores predeterminados que
funcionan de forma transparente al escribir una consulta o bucle ForEach en paralelo. En escenarios
ms avanzados, puede conectar su propio particionador.
Creacin de particiones diferentes
Hay muchas maneras de crear particiones de un origen de datos. En los enfoques ms eficaces, varios
subprocesos cooperan para procesar la secuencia original, en lugar de separar fsicamente el origen
en varias subsecuencias. Para matrices y otros orgenes indizados como colecciones IList donde de
antemano se conoce la longitud, la creacin de particiones por intervalos es la forma ms simple de
crear particiones. Cada subproceso recibe ndices iniciales y finales nicos, para poder procesar el
intervalo del origen sin sobrescribir ni ser sobrescrito por otro subproceso. La nica sobrecarga
implicada en la creacin de particiones por intervalos es el trabajo inicial de crear los intervalos;
ninguna sincronizacin adicional se requiere despus de eso. Por consiguiente, puede proporcionar
buen rendimiento siempre que la carga de trabajo se divida uniformemente. Una desventaja de la
creacin de particiones por intervalos es que si un subproceso finaliza pronto, no puede ayudar a los
dems a finalizar el trabajo.
Con listas vinculadas u otras colecciones cuya longitud no se conoce, puede utilizar la creacin de
particiones por fragmentos. En la creacin de particiones por fragmentos, cada subproceso o tarea de
un bucle o consulta en paralelo utiliza un nmero de elementos de origen de un fragmento, los
procesa y vuelve a recuperar ms elementos. Los particionadores se aseguran de que se distribuyen
todos los elementos y no hay ningn duplicado. Un fragmento puede tener cualquier tamao. Por
ejemplo, el particionador que se muestra en Cmo: Implementar las particiones dinmicas crea
fragmentos que contienen un solo elemento. Con tal de que los fragmentos no sean demasiado
grandes, esta forma de crear particiones mantiene inherentemente el equilibrio de carga porque la
asignacin de elementos a subprocesos no est predeterminada. Sin embargo, el particionador
incurre en la sobrecarga de sincronizacin cada vez que el subproceso necesita obtener otro
fragmento. La cantidad de sincronizacin en que se incurre en estos casos es inversamente
proporcional al tamao de los fragmentos.
En general, la creacin de particiones por intervalos solo es ms rpida cuando el tiempo de
ejecucin del delegado es de poco a moderado, el origen tiene un nmero grande de elementos y el
trabajo total de cada particin es aproximadamente equivalente. La creacin de particiones por
fragmentos es, por consiguiente, ms rpida en la mayora de los casos. En orgenes con un nmero
pequeo de elementos o tiempos de ejecucin ms largos para el delegado, el rendimiento de la
creacin de particiones por fragmentos e intervalos es casi igual.
Los particionadores de TPL tambin admiten un nmero de particiones dinmicas. Esto significa que
pueden crear particiones sobre la marcha, por ejemplo, cuando el bucle ForEach genera una nueva
tarea. Esta caracterstica permite al particionador escalar junto con el propio bucle. Los
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 95 de 107
particionadores dinmicos tambin mantienen inherentemente el equilibrio de carga. Cuando se crea
un particionador personalizado, se debe admitir que la creacin de particiones dinmicas se use
desde un bucle ForEach.
Configurar particionadores de equilibrio de carga para PLINQ
Algunas sobrecargas del mtodo Partitioner.Create permitan crear particionadores para una matriz u
origen IList y especificar si deba intentar equilibrar la carga de trabajo entre los subprocesos. Cuando
se configura el particionador para equilibrar la carga, se utiliza la creacin de particiones por
fragmentos y los elementos se presentan fuera de cada particin en pequeos fragmentos a medida
que se solicitan. Este enfoque ayuda a asegurar que todas las particiones tienen elementos para
procesar hasta que todo el bucle o la consulta se completa. Se puede utilizar una sobrecarga
adicional para proporcionar la creacin de particiones de equilibrio de carga de cualquier origen
IEnumerable.
En general, el equilibrio de carga exige que las particiones soliciten elementos con relativa frecuencia
de los particionadores. Por contraste, el particionador que crea particiones estticas puede asignar
todos los elementos a la vez mediante la creacin de particiones por intervalos o por fragmentos.
Esto requiere menos sobrecarga que el equilibrio de carga, pero podra llevar ms mucho tiempo
ejecutarse si un subproceso termina significativamente con ms trabajo que los dems. De forma
predeterminada cuando se pasa IList o una matriz, PLINQ siempre utiliza la creacin de particiones
por intervalos sin equilibrio de carga. Para habilitar el equilibrio de carga para PLINQ, use el mtodo
Partitioner.Create, como se muestra en el siguiente ejemplo.
' Static number of partitions requires indexable source.
Dim nums = Enumerable.Range(0, 100000000).ToArray()
' Create a load-balancing partitioner. Or specify false For Shared partitioning.
Dim customPartitioner = Partitioner.Create(nums, True)
' The partitioner is the query's data source.
Dim q = From x In customPartitioner.AsParallel()
Select x * Math.PI
q.ForAll(Sub(x) ProcessData(x))
La mejor manera de determinar si utilizar el equilibrio de carga en un escenario determinado es
experimentar y medir cunto tiempo tardan las operaciones en completarse con cargas y
configuraciones de equipo representativas. Por ejemplo, la creacin de particiones estticas podra
proporcionar un aumento de velocidad significativo en un equipo multiproceso con pocos ncleos,
pero podra ralentizar los equipos que tienen relativamente ms ncleos.
En la tabla siguiente se muestran las sobrecargas disponibles del mtodo Create. Estos
particionadores no estn limitados a su uso con PLINQ o ForEach. Tambin se pueden utilizar con
cualquier construccin paralela personalizada.
Sobrecarga Utiliza el equilibrio de carga
Create(Of TSource)(IEnumerable(Of
TSource))
Siempre
Create(Of TSource)(TSource(), Boolean)
Cuando el argumento booleano se especifica como
verdadero
Create(Of TSource)(IList(Of TSource),
Boolean)
Cuando el argumento booleano se especifica como
verdadero
Create(Int32, Int32) Nunca
Create(Int32, Int32, Int32) Nunca
Create(Int64, Int64) Nunca
Create(Int64, Int64, Int64) Nunca
Configurar particionadores por intervalos estticos para Parallel.ForEach
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 96 de 107
En un bucle For, el cuerpo del bucle se proporciona al mtodo como un delegado. El costo de invocar
ese delegado es ms o menos similar a una llamada al mtodo virtual. En algunos escenarios, el
cuerpo de un bucle paralelo podra ser lo bastante pequeo como para que el costo de la invocacin
del delegado en cada iteracin del bucle fuera significativa. En tales situaciones, puede utilizar una de
las sobrecargas Create para crear una IEnumerable(Of T) de particiones por intervalos de los
elementos de origen. Despus puede pasar esta coleccin de intervalos a un mtodo ForEach cuyo
cuerpo est compuesto de un bucle for normal. La ventaja de este enfoque es que solo se incurre en
el costo de invocacin de delegados una vez por intervalo, en lugar de una vez por elemento. En el
siguiente ejemplo se muestra el modelo bsico.
Imports System.Threading.Tasks
Imports System.Collections.Concurrent
Module PartitionDemo
Sub Main()
' Source must be array or IList.
Dim source = Enumerable.Range(0, 100000).ToArray()
' Partition the entire source array.
' Let the partitioner size the ranges.
Dim rangePartitioner = Partitioner.Create(0, source.Length)
Dim results(source.Length - 1) As Double
' Loop over the partitions in parallel. The Sub is invoked once per partition.
Parallel.ForEach(rangePartitioner, Sub(range, loopState)
' Loop over each range element without a delegate invocation.
For i As Integer = range.Item1 To range.Item2 - 1
results(i) = source(i) * Math.PI
Next
End Sub)
Console.WriteLine("Operation complete. Print results? y/n")
Dim input As Char = Console.ReadKey().KeyChar
If input = "y"c Or input = "Y"c Then
For Each d As Double In results
Console.Write("{0} ", d)
Next
End If
End Sub
End Module
Cada subproceso del bucle recibe su propio Tuple(Of T1, T2) que contiene los valores de ndice de
inicio y de fin del subintervalo especificado. El bucle for interno utiliza los valores toExclusive y
fromInclusive para recorrer directamente la matriz o IList.
Una de las sobrecargas Create permite especificar el tamao y el nmero de las particiones. Esta
sobrecarga se puede utilizar en escenarios donde el trabajo por elemento es tan bajo que incluso una
llamada al mtodo virtual por elemento tiene un impacto notable en el rendimiento.
Particionadores personalizados
En algunos escenarios, valdra la pena o incluso podra ser preciso implementar un particionador
propio. Por ejemplo, podra tener una clase de coleccin personalizada que puede crear particiones
ms eficazmente que los particionadores predeterminados, basndose en su conocimiento de la
estructura interna de la clase. O tal vez desee crear particiones por intervalos de tamaos diferentes
basndose en su conocimiento de cunto tiempo tardar en procesar los elementos en ubicaciones
diferentes de la coleccin de origen.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 97 de 107
Para crear un particionador personalizado bsico, derive una clase de
System.Collections.Concurrent.Partitioner(Of TSource) e invalide los mtodos virtuales, tal y como se
describe en la siguiente tabla.
GetPartitions
El subproceso principal llama a este mtodo una vez y devuelve
IList(IEnumerator(TSource)). Cada subproceso de trabajo del bucle o la
consulta puede llamar a GetEnumerator en la lista para recuperar
IEnumerator(Of T) de una particin distinta.
SupportsDynamicPartitions Devuelve true si implementa GetDynamicPartitions, de lo contrario, false.
GetDynamicPartitions
Si SupportsDynamicPartitions es true, se puede llamar a este mtodo
opcionalmente en lugar de a GetPartitions.
Si los resultados deben ser ordenables o si necesita acceso indizado a los elementos, derive de
System.Collections.Concurrent.OrderablePartitioner(Of TSource) e invalide sus mtodos virtuales tal y
como se describe en la siguiente tabla.
GetPartitions
El subproceso principal llama a este mtodo una vez y devuelve
IList(IEnumerator(TSource)). Cada subproceso de trabajo del bucle o
la consulta puede llamar a GetEnumerator en la lista para recuperar
IEnumerator(Of T) de una particin distinta.
SupportsDynamicPartitions
Devuelve true si implementa GetDynamicPartitions; de lo contrario,
falso.
GetDynamicPartitions Normalmente, solo llama a GetOrderableDynamicPartitions.
GetOrderableDynamicPartitions
Si SupportsDynamicPartitions es true, se puede llamar a este mtodo
opcionalmente en lugar de a GetPartitions.
En la siguiente tabla se proporcionan los detalles adicionales sobre cmo los tres tipos de
particionadores del equilibrio de carga implementan la clase OrderablePartitioner(Of TSource).
Propiedad o mtodo
IList / matriz
sin equilibrio
de carga
IList / matriz con
equilibrio de carga
IEnumerable
GetOrderablePartitions
Utiliza la
creacin de
particiones
por intervalos
Utiliza la creacin de
particiones por
fragmentos
optimizada para la
partitionCount
especificada
Utiliza la creacin de
particiones por
fragmentos y crea
un nmero de
particiones estticas.
OrderablePartitioner(Of
TSource).GetOrderableDynamicPartitions
Produce una
excepcin no
admitida
Utiliza la creacin de
particiones por
fragmentos
optimizada para las
listas y las particiones
dinmicas
Utiliza la creacin de
particiones por
fragmentos creando
un nmero de
particiones
dinmico.
KeysOrderedInEachPartition Devuelve true Devuelve true Devuelve true
KeysOrderedAcrossPartitions Devuelve true Devuelve false Devuelve false
KeysNormalized Devuelve true Devuelve true Devuelve true
SupportsDynamicPartitions
Devuelve
false
Devuelve true Devuelve true
Particiones dinmicas
Si piensa utilizar el particionador en un mtodo ForEach, debe poder devolver un nmero de
particiones dinmico. Esto significa que el particionador pueden proporcionar un enumerador para
una nueva particin a peticin en cualquier momento durante la ejecucin del bucle. Bsicamente,
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 98 de 107
cada vez que el bucle agrega una nueva tarea en paralelo, solicita una nueva particin para esa tarea.
Si exige que los datos se puedan ordenar, derive de
System.Collections.Concurrent.OrderablePartitioner(Of TSource) para que cada elemento de cada
particin tenga asignado un ndice nico.
Contrato para particionadores
Cuando implemente un particionador personalizado, siga estas instrucciones para asegurarse de que
la interaccin es correcta con PLINQ y ForEach en TPL:
Si se llama a GetPartitions con un argumento de cero o menos para partitionsCount, se
produce una ArgumentOutOfRangeException. Aunque PLINQ y TPL nunca pasarn en una
partitionCount igual a 0, recomendamos, no obstante, que se proteja ante esa posibilidad.
GetPartitions y GetOrderablePartitions siempre deberan devolver el nmero de particiones
partitionsCount. Si particionador se ejecuta fuera de los datos y no puede crear tantas
particiones como se solicitan, el mtodo debera devolver un enumerador vaco para cada
una de las particiones restantes. De lo contrario, PLINQ y TPL producirn una
InvalidOperationException.
GetPartitions, GetOrderablePartitions, GetDynamicPartitions y
GetOrderableDynamicPartitions nunca deberan devolver null (Nothing en Visual Basic). Si lo
hacen, PLINQ / TPL producirn una excepcin InvalidOperationException.
Los mtodos que devuelven particiones siempre deberan devolver particiones que puedan
enumerar completamente y de forma nica el origen de datos. No debera haber ninguna
duplicacin en el origen de datos ni elementos omitidos a menos que lo requiera
especficamente el particionador. Si no se sigue esta regla, se puede alterar el orden del
resultado.
Los siguientes captadores get booleanos siempre deben devolver con precisin los
siguientes valores para que no se altere el orden de salida:
o KeysOrderedInEachPartition: cada particin devuelve los elementos con ndices de
clave en aumento.
o KeysOrderedAcrossPartitions: para todas las particiones que se devuelven, los
ndices de la clave de la particin i son ms altos que los ndices de la clave en la
particin i-1.
o KeysNormalized: todos los ndices de clave aumentan consecutivamente,
comenzando por cero.
Todos los ndices deben ser nicos. No puede haber ndices duplicados. Si no se sigue esta
regla, se puede alterar el orden del resultado.
Todos los ndices deben ser no negativos. Si no se sigue esta regla, PLINQ/TPL pueden
producir excepciones.
5.1. Cmo: Implementar las particiones dinmicas
En el siguiente ejemplo se muestra cmo implementar un System.Collections.Concurrent.Orderable
Partitioner(Of TSource) personalizado que implementa la creacin de particiones dinmicas y que se
puede utilizar desde algunas sobrecargas de ForEach y de PLINQ.
Ejemplo
Cada vez que una particin llama a MoveNext en el enumerador, ste proporciona un elemento de
lista a la particin. En el caso de PLINQ y ForEach, la particin es una instancia de Task. Dado que las
solicitudes se producen simultneamente en varios subprocesos, se sincroniza el acceso al ndice
actual.
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Collections.Concurrent
Module Module1
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 99 de 107
Public Class OrderableListPartitioner(Of TSource)
Inherits OrderablePartitioner(Of TSource)
Private ReadOnly m_input As IList(Of TSource)
Public Sub New(ByVal input As IList(Of TSource))
MyBase.New(True, False, True)
m_input = input
End Sub
' Must override to return true.
Public Overrides ReadOnly Property SupportsDynamicPartitions As Boolean
Get
Return True
End Get
End Property
Public Overrides Function GetOrderablePartitions(ByVal partitionCount As Integer) As IList(Of IEnumerator(Of
KeyValuePair(Of Long, TSource)))
Dim dynamicPartitions = GetOrderableDynamicPartitions()
Dim partitions(partitionCount - 1) As IEnumerator(Of KeyValuePair(Of Long, TSource))
For i = 0 To partitionCount - 1
partitions(i) = dynamicPartitions.GetEnumerator()
Next
Return partitions
End Function
Public Overrides Function GetOrderableDynamicPartitions() As IEnumerable(Of KeyValuePair(Of Long, TSource))
Return New ListDynamicPartitions(m_input)
End Function
Private Class ListDynamicPartitions
Implements IEnumerable(Of KeyValuePair(Of Long, TSource))
Private m_input As IList(Of TSource)
Friend Sub New(ByVal input As IList(Of TSource))
m_input = input
End Sub
Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of Long, TSource)) Implements
IEnumerable(Of KeyValuePair(Of Long, TSource)).GetEnumerator
Return New ListDynamicPartitionsEnumerator(m_input)
End Function
Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
Return CType(Me, IEnumerable).GetEnumerator()
End Function
End Class
Private Class ListDynamicPartitionsEnumerator
Implements IEnumerator(Of KeyValuePair(Of Long, TSource))
Private m_input As IList(Of TSource)
Shared m_pos As Integer = 0
Private m_current As KeyValuePair(Of Long, TSource)
Public Sub New(ByVal input As IList(Of TSource))
m_input = input
m_pos = 0
Me.disposedValue = False
End Sub
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 100 de 107
Public ReadOnly Property Current As KeyValuePair(Of Long, TSource) Implements IEnumerator(Of
KeyValuePair(Of Long, TSource)).Current
Get
Return m_current
End Get
End Property
Public ReadOnly Property Current1 As Object Implements IEnumerator.Current
Get
Return Me.Current
End Get
End Property
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
Dim elemIndex = Interlocked.Increment(m_pos) - 1
If elemIndex >= m_input.Count Then
Return False
End If
m_current = New KeyValuePair(Of Long, TSource)(elemIndex, m_input(elemIndex))
Return True
End Function
Public Sub Reset() Implements IEnumerator.Reset
m_pos = 0
End Sub
Private disposedValue As Boolean ' To detect redundant calls
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
m_input = Nothing
m_current = Nothing
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class
End Class
Class ConsumerClass
Shared Sub Main()
Console.BufferHeight = 20000
Dim nums = Enumerable.Range(0, 2000).ToArray()
Dim partitioner = New OrderableListPartitioner(Of Integer)(nums)
' Use with Parallel.ForEach
Parallel.ForEach(partitioner, Sub(i) Console.Write("{0}:{1} ", i, Thread.CurrentThread.ManagedThreadId))
Console.WriteLine("PLINQ -----------------------------------")
' create a new partitioner, since Enumerators are not reusable.
Dim partitioner2 = New OrderableListPartitioner(Of Integer)(nums)
' Use with PLINQ
Dim query = From num In partitioner2.AsParallel()
Where num Mod 8 = 0
Select num
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 101 de 107
For Each v In query
Console.Write("{0} ", v)
Next
Console.WriteLine("press any key")
Console.ReadKey()
End Sub
End Class
End Module
ste es un ejemplo de creacin de particiones de fragmentos, y cada fragmento se compone de un
elemento. Proporcionando ms elementos a la vez, podra reducir la contencin sobre el bloqueo y
tericamente lograr un rendimiento ms rpido. Sin embargo, en algn punto, los fragmentos
mayores podran requerir lgica de equilibrio de carga adicional para mantener todos los
subprocesos ocupados hasta que se finalice todo el trabajo.

5.2. Cmo: Implementar un particionador con un nmero esttico de
particiones
En el siguiente ejemplo se muestra una manera de implementar un particionador personalizado
simple para PLINQ que realiza la creacin de particiones estticas. Dado que el particionador no
admite las particiones dinmicas, no se puede usar de Parallel.ForEach. Este particionador
determinado podra proporcionar ms velocidad que el particionador del intervalo predeterminado
para los orgenes de datos en los que cada elemento requiere una cantidad creciente de tiempo de
proceso.
Las particiones de este ejemplo estn basadas en la hiptesis de un aumento lineal del tiempo de
proceso por cada elemento. En la prctica, podra ser difcil predecir los tiempos de proceso de esta
manera. Si est utilizando un particionador esttico con un origen de datos concreto, puede
optimizar la frmula de creacin de particiones del origen, agregar lgica de equilibrio de carga o
emplear un enfoque de creacin de particiones de los fragmentos, como se muestra en Cmo:
Implementar las particiones dinmicas.

6. Generadores de tareas
Un generador de tareas se representa mediante la clase System.Threading.Tasks.TaskFactory, que
crea objetos Task, o la clase System.Threading.Tasks.TaskFactory(Of TResult), que crea objetos
Task(Of TResult). Ambas clases contienen mtodos que puede utilizar para:
Crear tareas e iniciarlas inmediatamente.
Crear continuaciones de tareas que se inicien cuando alguna o toda una matriz de tareas se
complete.
Crear tareas que representen pares de mtodos de comienzo/fin que siguen el Modelo de
programacin asincrnica.
La clase Task tiene una propiedad esttica que representa TaskFactory predeterminada.
Normalmente, los mtodos TaskFactory se invocan utilizando la propiedad Factory, como se muestra
en el siguiente ejemplo.
Dim taskA as Task = Task.Factory.StartNew(Sub( ...))
En la mayora de los casos, no tiene que derivar una nueva clase de TaskFactory. Sin embargo, a veces
es til configurar una nueva TaskFactory y utilizarla para especificar algunas opciones o asociar tareas
a un programador personalizado. En el siguiente ejemplo se muestra cmo configurar una nueva
TaskFactory que crea tareas que usan el TaskScheduler especificado y tiene las opciones
TaskCreationOptions especificadas.
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 102 de 107
Class Program
Shared Sub Main()
Dim cts As CancellationTokenSource = New CancellationTokenSource()
Dim _factory As TaskFactory = New TaskFactory(
cts.Token,
TaskCreationOptions.PreferFairness,
TaskContinuationOptions.ExecuteSynchronously,
New MyScheduler())
Dim t2 = _factory.StartNew(Sub() DoWork())
End Sub
Shared Sub DoWork()
End Sub

7. Programadores de tareas
Los programadores de tarea se representan mediante la clase System.Threading.Tasks.TaskScheduler.
Un programador de tareas se asegura de que se ejecuta el trabajo de una tarea. El programador de
tareas predeterminado est basado en .NET Framework 4 ThreadPool, que proporciona robo de
trabajo para el equilibrio de carga, inyeccin/retirada de subprocesos para obtener el mximo
resultado y un buen rendimiento en general. Debera ser suficiente para la mayora de los escenarios.
Sin embargo, si necesita funcionalidad especial, puede crear un programador personalizado y
habilitarlo para tareas o consultas concretas.
Programador de tareas predeterminado y ThreadPool
El programador predeterminado para Task Parallel Library y PLINQ utiliza .NET Framework
ThreadPool para poner en cola y ejecutar el trabajo. En .NET Framework 4, ThreadPool utiliza la
informacin que proporciona el tipo System.Threading.Tasks.Task para admitir el paralelismo
especfico (unidades efmeras de trabajo) que las tareas y consultas paralelas representan a menudo.
Cola global ThreadPool frente acolas locales
Como en versiones anteriores de .NET Framework, ThreadPool mantiene una cola de trabajo FIFO
(primero en llegar, primero en salir) global para los subprocesos en cada dominio de aplicacin.
Cuando un programa llama a QueueUserWorkItem (o UnsafeQueueUserWorkItem), el trabajo se
coloca en esta cola compartida y finalmente sale de la cola hacia el subproceso siguiente que est
disponible. En .NET Framework 4, esta cola se ha mejorado para utilizar un algoritmo sin bloqueo que
se parece la clase ConcurrentQueue. utilizando esta implementacin sin bloqueo, ThreadPool gasta
menos horario cuando pone en la cola y los elementos de trabajo de cola. Esta ventaja de
rendimiento est disponible para todos los programas que utilizan ThreadPool.
Las tareas de nivel superior, que son tareas que no se crean en el contexto de otra tarea, se colocan
en la cola global igual que cualquier otro elemento de trabajo. Sin embargo, las tareas anidadas o
secundarias, que se crean en el contexto de otra tarea, se controlan de forma bastante distinta. Una
tarea secundaria o anidada se coloca en una cola local que es especfica del subproceso en el que la
tarea primaria se est ejecutando. La tarea primaria puede ser una tarea de nivel superior o tambin
puede ser el elemento secundario de otra tarea. Cuando este subproceso est listo para ms trabajo,
primero busca en la cola local. Si hay elementos de trabajo esperando, se puede tener acceso a ellos
rpidamente. Se tiene acceso a las colas locales en el orden ltimo en entrar (LIFO), primero en salir
con el fin de conservar la situacin de la memoria cach y reducir la contencin.
En el siguiente ejemplo se muestran algunas tareas que se programan en la cola global y otras que se
programan en la cola local.
Sub QueueTasks()
' TaskA is a top level task.
Dim taskA = Task.Factory.StartNew(Sub()
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 103 de 107
Console.WriteLine("I was enqueued on the thread pool's global queue.")
' TaskB is a nested task and TaskC is a child task. Both go to local queue.
Dim taskB = New Task(Sub() Console.WriteLine("I was enqueued on the local queue."))
Dim taskC = New Task(Sub() Console.WriteLine("I was enqueued on the local queue, too."),
TaskCreationOptions.AttachedToParent)
taskB.Start()
taskC.Start()
End Sub)
End Sub
El uso de colas locales reduce no solo la presin en la cola global, tambin aprovecha la situacin de
los datos. Los elementos de trabajo de la cola local con frecuencia hacen referencia a estructuras de
datos que estn fsicamente cerca unos de otros en memoria. En estos casos, los datos ya estn en la
memoria cach despus de que la primera tarea se haya ejecutado, y se puede obtener acceso
rpidamente. LINQ Paralelo (PLINQ) y la clase Parallel usa tareas anidadas y tareas secundarias
extensivamente y logran aumentos significativos de velocidad utilizando las colas de trabajo locales.
Robo de trabajo
ThreadPool tambin representa un algoritmo de robo de trabajo para ayudar a asegurar que ningn
subproceso est inactivo mientras otros todava tienen trabajo en sus colas. Cuando un subproceso
ThreadPool est listo para ms trabajo, examina primero el encabezado de la cola local, a
continuacin, en la cola global y despus en las colas locales de otros subprocesos. Si encuentra un
elemento de trabajo en la cola local de otro subproceso, aplica primero heurstica para asegurarse de
que puede ejecutar el trabajo eficazmente. Si puede, quita el elemento de trabajo de la cola (en
orden FIFO). Esto reduce la contencin en cada cola local y conserva la situacin de los datos. Esta
arquitectura ayuda el equilibrio de la carga trabajo ms eficazmente que las versiones pasadas
hicieron.
Tareas de ejecucin prolongada
Tal vez le interese evitar explcitamente que una tarea se coloque en una cola local. Por ejemplo,
puede saber que un elemento de trabajo determinado se ejecutar durante un tiempo relativamente
largo y es probable que bloquee el resto de los elementos de trabajo de la cola local. En este caso,
puede especificar la opcin LongRunning, que proporciona una sugerencia al programador que le
indica que tal vez es necesario un subproceso adicional para que la tarea no bloquee el progreso de
otros subprocesos o elementos de trabajo de la cola local. Utilizando esta opcin, se evita
ThreadPool completamente, incluidas las colas global y locales.
Inclusin de tareas
En algunos casos, cuando se espera un tarea, se puede ejecutar sincrnicamente en el subproceso
que est realizando la operacin de espera. Esto mejora el rendimiento, porque evita la necesidad de
un subproceso adicional mediante el uso del subproceso existente que, de otro modo, se habra
bloqueado. Para evitar errores despus de volver a entrar, la inclusin de tareas solo tiene lugar
cuando el destino de la espera se encuentra en la cola local del subproceso pertinente.
Especificar un contexto de sincronizacin
Puede utilizar el mtodo TaskScheduler.FromCurrentSynchronizationContext para especificar que una
tarea se debera programar para ejecutarse en un subproceso determinado. Esto es til en marcos
como Windows Forms y Windows Presentation Foundation, donde el acceso a los objetos de interfaz
de usuario est restringido a menudo para el cdigo que se est ejecutando en el mismo subproceso
en el que se cre el objeto UI.

7.1. Cmo: Crear un programador de tareas que limita el grado de
simultaneidad
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 104 de 107
En algunos casos no muy usuales, podra lograr aumentar el rendimiento creando un programador
de tareas personalizado que se derive de la clase System.Threading.Tasks.TaskScheduler. Despus
podra especificar este programador en un mtodo For o ForEach utilizando la enumeracin
System.Threading.Tasks.ParallelOptions. Al utilizar los objetos Task directamente, puede especificar el
programador personalizado mediante el constructor TaskFactory que toma TaskScheduler como un
parmetro de entrada o por algn otro medio como StartNew.
Tambin puede utilizar un programador personalizado para lograr la funcionalidad que el
programador predeterminado no proporciona, como es el orden de ejecucin estricto de primero en
entrar, primero en salir (FIFO). En el ejemplo siguiente se muestra cmo crear un programador de
tareas personalizado. Este programador permite especificar el grado de simultaneidad.
Ejemplo
El siguiente ejemplo procede de Parallel Extensions Samples del sitio web Galera de cdigo de
MSDN.
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Threading
Imports System.Threading.Tasks
Module Module2
Sub Main()
' Create a scheduler that uses only one thread.
Dim lcts As New LimitedConcurrencyLevelTaskScheduler(1)
' Create a TaskFactory and pass it our custom scheduler.
Dim factory As New TaskFactory(lcts)
Dim cts As New CancellationTokenSource()
' Use our factory to run a task.
Dim t As Task = factory.StartNew(Sub()
For i As Integer = 1 To 50000
Console.Write("{0} on thread {1}. ", i, Thread.CurrentThread.ManagedThreadId)
Next
End Sub,
cts.Token)
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
''' <summary>
''' Provides a task scheduler that ensures a maximum concurrency level While running on top of the ThreadPool.
''' </summary>
Public Class LimitedConcurrencyLevelTaskScheduler
Inherits TaskScheduler
''' <summary>Whether the current thread is processing work items.</summary>
<ThreadStatic()>
Private Shared _currentThreadIsProcessingItems As Boolean
''' <summary>The list of tasks to be executed.</summary>
Private ReadOnly _tasks As LinkedList(Of Task) = New LinkedList(Of Task)() ' protected by lock(_tasks)
''' <summary>The maximum concurrency level allowed by this scheduler.</summary>
Private ReadOnly _maxDegreeOfParallelism As Integer
''' <summary>Whether the scheduler is currently processing work items.</summary>
Private _delegatesQueuedOrRunning As Integer = 0 ' protected by lock(_tasks)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 105 de 107
''' <summary>
''' Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the specified degree of
parallelism.
''' </summary>
''' <param name="maxDegreeOfParallelism">The maximum degree of parallelism provided by this
scheduler.</param>
Public Sub New(ByVal maxDegreeOfParallelism As Integer)
If (maxDegreeOfParallelism < 1) Then
Throw New ArgumentOutOfRangeException("maxDegreeOfParallelism")
End If
_maxDegreeOfParallelism = maxDegreeOfParallelism
End Sub
''' <summary>Queues a task to the scheduler.</summary>
''' <param name="t">The task to be queued.</param>
Protected Overrides Sub QueueTask(ByVal t As Task)
' Add the task to the list of tasks to be processed. If there aren't enough
' delegates currently queued or running to process tasks, schedule another.
SyncLock (_tasks)
_tasks.AddLast(t)
If (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) Then
_delegatesQueuedOrRunning = _delegatesQueuedOrRunning + 1
NotifyThreadPoolOfPendingWork()
End If
End SyncLock
End Sub
''' <summary>
''' Informs the ThreadPool that there's work to be executed for this scheduler.
''' </summary>
Private Sub NotifyThreadPoolOfPendingWork()
ThreadPool.UnsafeQueueUserWorkItem(Sub()
' Note that the current thread is now processing work items.
' This is necessary to enable inlining of tasks into this thread.
_currentThreadIsProcessingItems = True
Try
' Process all available items in the queue.
While (True)
Dim item As Task
SyncLock (_tasks)
' When there are no more items to be processed,
' note that we're done processing, and get out.
If (_tasks.Count = 0) Then
_delegatesQueuedOrRunning = _delegatesQueuedOrRunning - 1
Exit While
End If
' Get the next item from the queue
item = _tasks.First.Value
_tasks.RemoveFirst()
End SyncLock
' Execute the task we pulled out of the queue
MyBase.TryExecuteTask(item)
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 106 de 107
End While
' We're done processing items on the current thread
Finally
_currentThreadIsProcessingItems = False
End Try
End Sub,
Nothing)
End Sub
''' <summary>Attempts to execute the specified task on the current thread.</summary>
''' <param name="task">The task to be executed.</param>
''' <param name="taskWasPreviouslyQueued"></param>
''' <returns>Whether the task could be executed on the current thread.</returns>
Protected Overrides Function TryExecuteTaskInline(ByVal t As Task, ByVal taskWasPreviouslyQueued As
Boolean) As Boolean
' If this thread isn't already processing a task, we don't support inlining
If (Not _currentThreadIsProcessingItems) Then
Return False
End If
' If the task was previously queued, remove it from the queue
If (taskWasPreviouslyQueued) Then
TryDequeue(t)
End If
' Try to run the task.
Return MyBase.TryExecuteTask(t)
End Function
''' <summary>Attempts to remove a previously scheduled task from the scheduler.</summary>
''' <param name="t">The task to be removed.</param>
''' <returns>Whether the task could be found and removed.</returns>
Protected Overrides Function TryDequeue(ByVal t As Task) As Boolean
SyncLock (_tasks)
Return _tasks.Remove(t)
End SyncLock
End Function
''' <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
Public Overrides ReadOnly Property MaximumConcurrencyLevel As Integer
Get
Return _maxDegreeOfParallelism
End Get
End Property
''' <summary>Gets an enumerable of the tasks currently scheduled on this scheduler.</summary>
''' <returns>An enumerable of the tasks currently scheduled.</returns>
Protected Overrides Function GetScheduledTasks() As IEnumerable(Of Task)
Dim lockTaken As Boolean = False
Try
Monitor.TryEnter(_tasks, lockTaken)
If (lockTaken) Then
Return _tasks.ToArray()
Else
Throw New NotSupportedException()
End If
Programacin Paralela en .NET Framework
MCT: Luis Dueas Pag 107 de 107
Finally
If (lockTaken) Then
Monitor.Exit(_tasks)
End If
End Try
End Function
End Class
End Module

Das könnte Ihnen auch gefallen