Procedimiento para escribir un bucle Parallel.ForEach con variables locales de partición

En el ejemplo siguiente se muestra cómo escribir un método ForEach en el que se usan variables locales de partición. Cuando un bucle ForEach se ejecuta, divide su colección de origen en varias particiones. Cada partición tiene su propia copia de la variable local de partición. Una variable local de partición es similar a una variable local de subproceso, excepto por el hecho de que varias particiones se pueden ejecutar en un único subproceso.

El código y los parámetros de este ejemplo se parecen mucho al método For correspondiente. Para obtener más información, vea Cómo: Escribir un bucle Parallel.For con variables locales de subproceso.

Para usar una variable local de partición en un bucle ForEach, debe llamar a una de las sobrecargas del método que toma dos parámetros de tipo. El primer parámetro de tipo, TSource, especifica el tipo del elemento de origen, mientras que el segundo parámetro de tipo, TLocal, especifica el tipo de la variable local de partición.

Ejemplo

En el ejemplo siguiente se realiza una llamada a la sobrecarga de Parallel.ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) para calcular la suma de una matriz de un millón de elementos. Esta sobrecarga gráfico tiene cuatro parámetros:

  • source, que es el origen de datos. Debe implementar IEnumerable<T>. El origen de datos de este ejemplo es un objeto IEnumerable<Int32> de un millón de miembros que ha sido devuelto por el método Enumerable.Range.

  • localInit, o bien la función que inicializa la variable local de partición. Esta función se llama una vez por cada partición en la que se ejecuta la operación Parallel.ForEach. En el ejemplo se inicializa la variable local de partición en cero.

  • body, un Func<T1,T2,T3,TResult> al que invoca el bucle paralelo en cada iteración del mismo bucle. Su firma es Func\<TSource, ParallelLoopState, TLocal, TLocal>. Se proporciona el código para el delegado y el bucle pasa los parámetros de entrada, que son:

    • El elemento actual de IEnumerable<T>.

    • Una variable ParallelLoopState que se puede usar en el código del delegado a fin de examinar el estado del bucle.

    • La variable local de partición.

    El delegado devuelve la variable local de partición, y esta se pasa luego a la siguiente iteración del bucle que se ejecuta en esa partición concreta. Cada una de las particiones del bucle mantiene una instancia independiente de esta variable.

    En el ejemplo, el delegado agrega el valor de cada entero a la variable local de partición, que mantiene un total acumulado de los valores de los elementos enteros de esa partición.

  • localFinally, un delegado Action<TLocal> al que invoca Parallel.ForEach cuando se han completado las operaciones de bucle en las particiones. El método Parallel.ForEach pasa al delegado Action<TLocal> el valor final de la variable local de partición correspondiente a esta partición del bucle, y usted proporciona el código que realiza la acción necesaria para combinar el resultado de esta partición con los resultados de las otras. Varias tareas pueden invocar simultáneamente a este delegado. Debido a esto, en el ejemplo se usa el método Interlocked.Add(Int32, Int32) se utiliza para sincronizar el acceso a la variable total. Como el tipo de delegado es Action<T>, no hay valor devuelto.

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

class Test
{
    static void Main()
    {
        int[] nums = Enumerable.Range(0, 1000000).ToArray();
        long total = 0;

        // First type parameter is the type of the source elements
        // Second type parameter is the type of the thread-local variable (partition subtotal)
        Parallel.ForEach<int, long>(
            nums, // source collection
            () => 0, // method to initialize the local variable
            (j, loop, subtotal) => // method invoked by the loop on each iteration
            {
                subtotal += j; //modify local variable
                return subtotal; // value to be passed to next iteration
            },
            // Method to be executed when each partition has completed.
            // finalResult is the final value of subtotal for a particular partition.
            (finalResult) => Interlocked.Add(ref total, finalResult));

        Console.WriteLine("The total from Parallel.ForEach is {0:N0}", total);
    }
}
// The example displays the following output:
//        The total from Parallel.ForEach is 499,999,500,000
' 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 parameter is the type of the source elements
        ' Second type parameter is the type of the thread-local variable (partition subtotal)
        Parallel.ForEach(Of Integer, Long)(nums, Function() 0,
                                           Function(elem, loopState, subtotal)
                                               subtotal += elem
                                               Return subtotal
                                           End Function,
                                            Sub(finalResult)
                                                Interlocked.Add(total, finalResult)
                                            End Sub)

        Console.WriteLine("The result of Parallel.ForEach is {0:N0}", total)
    End Sub
End Module
' The example displays the following output:
'       The result of Parallel.ForEach is 499,999,500,000

Vea también