Procedura: scrivere un ciclo Parallel.For con variabili di thread locali

Questo esempio illustra come usare le variabili locali di thread per archiviare e recuperare lo stato in ogni attività separata creata da un ciclo For. L'uso dei dati locali di thread permette di evitare il sovraccarico dovuto alla sincronizzazione di un numero elevato di accessi a uno stato condiviso. Invece di scrivere in una risorsa condivisa a ogni iterazione, si calcola e si archivia il valore fino al completamento di tutte le iterazioni per l'attività. È quindi possibile scrivere il risultato finale una volta sola nella risorsa condivisa oppure passarlo a un altro metodo.

Esempio

L'esempio seguente chiama 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 all'indice corrispondente.

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

I primi due parametri di ogni metodo For specificano i valori di iterazione iniziali e finali. In questo overload del metodo, il terzo parametro corrisponde alla posizione di inizializzazione dello stato locale. In questo contesto lo stato locale indica una variabile la cui durata si estende da immediatamente prima della prima iterazione del ciclo nel thread corrente a immediatamente dopo l'ultima iterazione.

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

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

Il quarto parametro definisce la logica del ciclo. Deve essere un delegato o un'espressione lambda con 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 di cicli per quell'iterazione del ciclo. Il secondo è un oggetto ParallelLoopState che può essere usato per uscire dal ciclo. L'oggetto è fornito dalla classe Parallel a ogni occorrenza del ciclo. Il terzo parametro è la variabile locale di thread. L'ultimo parametro è il tipo restituito. In questo caso il tipo è Int64, poiché questo è il tipo specificato nell'argomento tipo For. Questa variabile è denominata subtotal ed è restituita dall'espressione lambda. Il valore restituito è usato per inizializzare subtotal in ogni iterazione successiva del ciclo. È anche possibile considerare quest'ultimo parametro come un valore passato a ogni iterazione e quindi passato al delegato localFinally al completamento dell'ultima iterazione.

Il quinto parametro definisce il metodo chiamato una volta, dopo il completamento di tutte le iterazioni in un determinato thread. Il tipo di 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 è aggiunto a una variabile nell'ambito delle classi in un modo thread-safe chiamando il metodo Interlocked.Add. L'uso di una variabile locale di thread permette di evitare di scrivere nella variabile della classe a ogni iterazione del ciclo.

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

Vedi anche