Como: Escrever um loop Parallel.For com variáveis Thread-Local

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

Exemplo

O exemplo a seguir chama o For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) método para calcular a soma dos valores em uma matriz que contém um milhão de elementos. O valor de cada elemento é igual ao 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 cada For método especificam os valores de iteração inicial e final. Nessa sobrecarga do método, o terceiro parâmetro é onde você inicializa seu estado local. Neste contexto, estado local significa uma variável cujo tempo de vida se estende de pouco antes da primeira iteração do loop no thread atual, até logo após a última iteração.

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

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

O quarto parâmetro define a lógica do loop. Deve ser uma expressão delegada ou lambda cuja assinatura esteja Func<int, ParallelLoopState, long, long> em C# ou Func(Of Integer, ParallelLoopState, Long, Long) em Visual Basic. O primeiro parâmetro é o valor do contador de loop para essa iteração do loop. O segundo é um ParallelLoopState objeto que pode ser usado para sair do loop, este objeto é fornecido pela Parallel classe para cada ocorrência do loop. O terceiro parâmetro é a variável thread-local. O último parâmetro é o tipo de retorno. Nesse caso, o tipo é Int64 porque esse é o tipo que especificamos no For argumento type. Essa variável é nomeada subtotal e retornada pela expressão lambda. O valor de retorno é usado para inicializar subtotal em cada iteração subsequente do loop. Você também pode pensar nesse último parâmetro como um valor que é passado para cada iteração e, em seguida, passado para o localFinally delegado quando a última iteração é concluída.

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

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

Consulte também