Procedimiento para escribir un bucle Parallel.For con variables locales de subproceso

En este ejemplo se muestra la forma de usar variables locales para el subproceso para almacenar y recuperar el estado en cada una de las tareas independientes creadas por un bucle For. Mediante el uso de datos locales de subproceso, se puede evitar la sobrecarga que supone la sincronización de un gran número de accesos a un estado compartido. En vez de escribir en un recurso compartido en cada iteración, se calcula y se almacena el valor hasta que finalizan todas las iteraciones de la tarea. A continuación, se escribe una vez el resultado final en el recurso compartido o se pasa a otro método.

Ejemplo

En el ejemplo siguiente se llama al método For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) para calcular la suma de los valores de una matriz que contiene un millón de elementos. El valor de cada elemento es igual a su índice.

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

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

        // Use type parameter to make subtotal a long, not an int
        Parallel.For<long>(0, nums.Length, () => 0,
            (j, loop, subtotal) =>
            {
                subtotal += nums[j];
                return subtotal;
            },
            subtotal => Interlocked.Add(ref total, subtotal));

        Console.WriteLine("The total is {0:N0}", total);
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}
'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(subtotal) Interlocked.Add(total, subtotal))

        Console.WriteLine("The total is {0:N0}", total)
        Console.WriteLine("Press any key to exit")
        Console.ReadKey()
    End Sub

End Module

Los dos primeros parámetros de cada método For especifican los valores de iteración inicial y final. En esta sobrecarga del método, el tercer parámetro es donde se inicializa el estado local. En este contexto, estado local significa una variable cuya duración se extiende desde justo antes de la primera iteración del bucle en el subproceso actual, hasta justo después de la última iteración.

El tipo del tercer parámetro es Func<TResult>, donde TResult es el tipo de la variable que almacenará el estado local de subproceso. Su tipo se define mediante el argumento de tipo genérico que se proporciona al llamar al método For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) genérico, que en este caso es Int64. El argumento de tipo le indica al compilador el tipo de variable temporal que se usará para almacenar el estado local de subproceso. En este ejemplo, la expresión () => 0 (o Function() 0 en Visual Basic) inicializa la variable local de subproceso en cero. Si el argumento de tipo genérico es un tipo de referencia o un tipo de valor definido por el usuario, la expresión tendrá el aspecto siguiente:

() => new MyClass()  
Function() new MyClass()  

El cuarto parámetro define la lógica del bucle y debe ser un delegado o una expresión lambda cuya firma sea Func<int, ParallelLoopState, long, long> en C# o Func(Of Integer, ParallelLoopState, Long, Long) en Visual Basic. El primer parámetro es el valor del contador de bucle correspondiente a esa iteración del bucle. El segundo es un objeto ParallelLoopState que se puede usar para desglosar el bucle; la clase Parallel proporciona este objeto a cada repetición del bucle. El tercer parámetro es la variable local de subproceso. El último parámetro es el tipo de valor devuelto. En este caso, el tipo es Int64 porque es el que se especificó en el argumento de tipo For. Esa variable se denomina subtotal y la devuelve la expresión lambda. El valor devuelto se usa para inicializar subtotal en todas las iteraciones posteriores del bucle. Este último parámetro también se puede considerar como un valor que se pasa a todas las iteraciones y que, cuando finaliza la última iteración, se pasa al delegado localFinally.

El quinto parámetro define el método que se llama una vez, después de que se hayan completado todas las iteraciones de un subproceso en particular. El tipo del argumento de entrada se corresponde de nuevo con el argumento de tipo del método For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) y el tipo devuelto por la expresión lambda del cuerpo. En este ejemplo, el valor se agrega a una variable en el ámbito de clase de un modo seguro para subprocesos mediante una llamada al método Interlocked.Add. Gracias a la variable local de subproceso, se ha evitado el tener que escribir en esta variable de clase en cada iteración del bucle.

Para obtener más información sobre cómo usar las expresiones lambda, vea Expresiones lambda en PLINQ y TPL.

Vea también