Partilhar via


Como criar 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 loop de For. 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 método For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) 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 {total:N0}");
        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 método For 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 For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) genérico, 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 é Func<int, ParallelLoopState, long, long> em C# ou Func(Of Integer, ParallelLoopState, Long, Long) no Visual Basic. O primeiro parâmetro é o valor do contador de loop para essa iteração do loop. O segundo é um objeto ParallelLoopState que pode ser usado para sair do loop; Este objeto é fornecido pela classe Parallel 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 argumento For type. Essa variável é chamada 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 delegado localFinally quando a última iteração for 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 de entrada também 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 corpo. Neste exemplo, o valor é adicionado a uma variável no escopo da classe de forma segura chamando o método Interlocked.Add. Usando uma variável thread-local, evitamos escrever na 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.

Ver também