Procedura: Scrivere un ciclo Parallel.ForEach con variabili partition-local

L'esempio seguente illustra come scrivere un metodo ForEach che usa variabili partition-local. Quando viene eseguito un ciclo ForEach, la relativa raccolta di origine viene divisa in più partizioni. Ogni partizione ha la propria copia della variabile partition-local. Una variabile partition-local è simile a una variabile thread-local, tranne per il fatto che in un singolo thread possono essere eseguite più partizioni.

Il codice e i parametri riportati in questo esempio, somigliano molto al metodo For corrispondente. Per altre informazioni, vedere Procedura: scrivere un ciclo Parallel.For con variabili di thread locali.

Per usare una variabile partition-local in un ciclo ForEach, è necessario chiamare uno degli overload del metodo che accetta due parametri di tipo. Il primo parametro di tipo, TSource, specifica il tipo di elemento di origine, mentre il secondo parametro di tipo, TLocal, specifica il tipo di variabile partition-local.

Esempio

Nell'esempio seguente viene chiamato l'overload Parallel.ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) per calcolare la somma di una matrice di un milione di elementi. L'overload ha quattro parametri:

  • source, ovvero l'origine dati. Deve implementare IEnumerable<T>. L'origine dati nell'esempio è l'oggetto IEnumerable<Int32> da un milione di membri restituito dal metodo Enumerable.Range.

  • localInit, ovvero la funzione che inizializza la variabile partition-local. Questa funzione viene chiamata una volta per ogni partizione in cui viene eseguita l'operazione Parallel.ForEach. Nell'esempio, la variabile partition-local viene inizializzata su zero.

  • body, un delegato Func<T1,T2,T3,TResult> che viene richiamato dal ciclo parallelo per ogni iterazione del ciclo. La firma è Func\<TSource, ParallelLoopState, TLocal, TLocal>. Si fornisce il codice per il delegato e il ciclo passa i parametri di input, che sono:

    • L'elemento corrente dell'oggetto IEnumerable<T>.

    • Una variabile ParallelLoopState che si può usare nel codice del delegato per esaminare lo stato del ciclo.

    • La variabile partition-local.

    Il delegato restituisce la variabile partition-local, che viene quindi passata all'iterazione successiva del ciclo che viene eseguito in quella partizione specifica. Ogni partizione del ciclo mantiene un'istanza separata di questa variabile.

    Nell'esempio il delegato aggiunge il valore di ogni valore integer alla variabile partition-local, che mantiene un totale corrente dei valori degli elementi integer in quella partizione.

  • localFinally, un delegato Action<TLocal> che viene richiamato da Parallel.ForEach al termine delle operazioni di riproduzione del ciclo in ogni partizione. Il metodo Parallel.ForEach passa al delegato Action<TLocal> il valore finale della variabile partition-local per questa partizione del ciclo e l'utente fornisce il codice che esegue l'azione necessaria per la combinazione del risultato di questa partizione con i risultati delle altre partizioni. Questo delegato può essere chiamato simultaneamente da più attività. Per questo motivo, nell'esempio viene usato il metodo Interlocked.Add(Int32, Int32) per sincronizzare l'accesso alla variabile total. Poiché il tipo del delegato è Action<T>, non viene restituito alcun valore.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Test
{
    static void Main()
    {
        int[] nums = Enumerable.Range(0, 1000000).ToArray();
        long total = 0;

        // First type parameter is the type of the source elements
        // Second type parameter is the type of the thread-local variable (partition subtotal)
        Parallel.ForEach<int, long>(
            nums, // source collection
            () => 0, // method to initialize the local variable
            (j, loop, subtotal) => // method invoked by the loop on each iteration
            {
                subtotal += j; //modify local variable
                return subtotal; // value to be passed to next iteration
            },
            // Method to be executed when each partition has completed.
            // finalResult is the final value of subtotal for a particular partition.
            (finalResult) => Interlocked.Add(ref total, finalResult));

        Console.WriteLine("The total from Parallel.ForEach is {0:N0}", total);
    }
}
// The example displays the following output:
//        The total from Parallel.ForEach is 499,999,500,000
' How to: Write a Parallel.ForEach Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForEachThreadLocal
    Sub Main()

        Dim nums() As Integer = Enumerable.Range(0, 1000000).ToArray()
        Dim total As Long = 0

        ' First type parameter is the type of the source elements
        ' Second type parameter is the type of the thread-local variable (partition subtotal)
        Parallel.ForEach(Of Integer, Long)(nums, Function() 0,
                                           Function(elem, loopState, subtotal)
                                               subtotal += elem
                                               Return subtotal
                                           End Function,
                                            Sub(finalResult)
                                                Interlocked.Add(total, finalResult)
                                            End Sub)

        Console.WriteLine("The result of Parallel.ForEach is {0:N0}", total)
    End Sub
End Module
' The example displays the following output:
'       The result of Parallel.ForEach is 499,999,500,000

Vedi anche