Como: Gravar um loop Parallel.For com variáveis locais de thread

Este exemplo mostra como usar variáveis de thread local para armazenar e recuperar o estado em cada tarefa separada criada por um loop For. Usando dados de thread local, você pode evitar a sobrecarga de sincronizar um grande número de acessos com estado compartilhado. Em vez de gravar em um recurso compartilhado em cada iteração, você computa e armazena o valor até todas as iterações da tarefas serem concluídas. Em seguida, é possível gravar o resultado final uma vez no recurso compartilhado ou passá-lo para outro método.

Exemplo

O exemplo a seguir chama o método For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) para calcular a soma dos valores em uma matriz com um milhão de elementos. O valor de cada elemento é igual a seu í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

Os dois primeiros parâmetros de todo método For especificam os valores de iteração iniciais e finais. Nessa sobrecarga do método, o terceiro parâmetro é onde o estado local é inicializado. Nesse contexto, estado local significa uma variável cujo tempo de vida se prolonga um pouco antes da primeira iteração do loop no thread atual até pouco depois da última iteração.

O tipo do terceiro parâmetro é um Func<TResult>, em que TResult é o tipo da variável que armazenará o estado de thread local. Seu tipo é definido pelo argumento de tipo genérico fornecido durante a chamada do método For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) genérico, que, neste caso é Int64. O argumento de tipo informa ao compilador o tipo da variável temporária que será usada para armazenar o estado de thread local. Neste exemplo, a expressão () => 0 (ou Function() 0 no Visual Basic) inicializa a variável de thread local em zero. Se o argumento de tipo genérico fosse um tipo de referência ou um tipo de valor definido pelo usuário, a expressão seria assim:

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

O quarto parâmetro define a lógica do loop. Ele deve ser uma expressão representante ou lambda, cuja assinatura é Func<int, ParallelLoopState, long, long> no C# ou Func(Of Integer, ParallelLoopState, Long, Long) no Visual Basic. O primeiro parâmetro é o valor do contador de loops para essa iteração do loop. O segundo é um objeto ParallelLoopState que pode ser usado para interromper o loop. Esse objeto é fornecido pela classe Parallel para cada ocorrência do loop. O terceiro parâmetro é a variável de thread local. O último parâmetro é o tipo de retorno. Neste caso, o tipo é Int64 porque esse é o tipo que especificamos no argumento do tipo For. Essa variável se chama subtotal e é retornada pela expressão lambda. O valor retornado é usado para inicializar subtotal em toda iteração subsequente do loop. Também é possível considerar esse último parâmetro como um valor passado para cada iteração e, em seguida, passado para o representante localFinally quando a última iteração está concluída.

O quinto parâmetro define o método chamado uma vez, depois que todas as iterações em um determinado thread foram concluídas. O tipo de argumento de entrada mais uma vez corresponde ao argumento de tipo do método For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) e ao tipo retornado pela expressão lambda do corpo. Neste exemplo, o valor é adicionado a uma variável no escopo da classe em thread-safe chamando o método Interlocked.Add. Usando uma variável de thread local, evitamos a gravação nessa variável de classe em toda iteração do loop.

Para obter mais informações sobre como usar expressões lambda, confira Expressões lambda no PLINQ e TPL.

Confira também