Condividi tramite


Come scrivere un ciclo Parallel.For con variabili di tipo Thread-Local

Questo esempio illustra come usare le variabili locali del thread per archiviare e recuperare lo stato in ogni attività separata creata da un ciclo For. Usando i dati locali del thread, è possibile evitare il sovraccarico della sincronizzazione di un numero elevato di accessi allo stato condiviso. Anziché scrivere in una risorsa condivisa in ogni iterazione, si calcola e si archivia il valore fino al termine di tutte le iterazioni per l'attività. È quindi possibile scrivere il risultato finale una volta nella risorsa condivisa o passarlo a un altro metodo.

Esempio

Nell'esempio seguente viene chiamato il metodo For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) per calcolare la somma dei valori in una matrice contenente un milione di elementi. Il valore di ogni elemento è uguale al relativo indice.

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

I primi due parametri di ogni metodo For specificano i valori di iterazione iniziale e finale. In questa versione sovraccaricata del metodo, il terzo parametro è dove inizializzi il tuo stato locale. In questo contesto, lo stato locale indica una variabile la cui durata si estende da poco prima della prima iterazione del ciclo nel thread corrente, fino a subito dopo l'ultima iterazione.

Il tipo del terzo parametro è un Func<TResult> in cui TResult è il tipo della variabile che archivierà lo stato locale del thread. Il tipo è definito dall'argomento di tipo generico fornito quando si chiama il metodo di For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) generico, che in questo caso è Int64. L'argomento type indica al compilatore il tipo della variabile temporanea che verrà usata per archiviare lo stato locale del thread. In questo esempio l'espressione () => 0 (o Function() 0 in Visual Basic) inizializza la variabile thread-local su zero. Se l'argomento di tipo generico è un tipo riferimento o un tipo valore definito dall'utente, l'espressione sarà simile alla seguente:

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

Il quarto parametro definisce la logica del ciclo. Deve essere un delegato o un'espressione lambda la cui firma è Func<int, ParallelLoopState, long, long> in C# o Func(Of Integer, ParallelLoopState, Long, Long) in Visual Basic. Il primo parametro è il valore del contatore del ciclo per quell'iterazione. Il secondo è un oggetto ParallelLoopState che può essere utilizzato per interrompere il ciclo; questo oggetto viene fornito dalla classe Parallel a ogni occorrenza del ciclo. Il terzo parametro è la variabile thread-local. L'ultimo parametro è il tipo restituito. In questo caso, il tipo è Int64 perché è il tipo specificato nell'argomento del tipo For. Tale variabile è denominata subtotal e viene restituita dall'espressione lambda. Il valore restituito viene usato per inizializzare subtotal in ogni iterazione successiva del ciclo. È anche possibile considerare questo ultimo parametro come valore passato a ogni iterazione e quindi passato al delegato localFinally al termine dell'ultima iterazione.

Il quinto parametro definisce il metodo chiamato una volta, dopo che tutte le iterazioni in un determinato thread sono state completate. Il tipo dell'argomento di input corrisponde di nuovo all'argomento di tipo del metodo For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) e al tipo restituito dall'espressione lambda del corpo. In questo esempio il valore viene aggiunto a una variabile nell'ambito della classe in modo thread-safe chiamando il metodo Interlocked.Add. Usando una variabile thread-local, è stata evitata la scrittura in questa variabile di classe in ogni iterazione del ciclo.

Per altre informazioni su come usare espressioni lambda, vedere espressioni lambda in PLINQ e TPL.

Vedere anche