Vorgehensweise: Schreiben einer Parallel.ForEach-Schleife mit partitionslokalen Variablen

Im folgenden Beispiel wird veranschaulicht, wie eine ForEach-Methode geschrieben wird, für die partitionslokale Variablen verwendet werden. Wenn eine ForEach-Schleife ausgeführt wird, wird die Quellauflistung in mehrere Partitionen unterteilt. Jede Partition besitzt eine eigene Kopie der partitionslokalen Variable. Eine partitionslokale Variable ähnelt einer threadlokalen Variable. Der Unterschied besteht darin, dass mehrere Partitionen in einem einzelnen Thread ausgeführt werden können.

Der Code und die Parameter in diesem Beispiel ähneln stark der entsprechenden For-Methode. Weitere Informationen finden Sie unter Vorgehensweise: Schreiben einer Parallel.For-Schleife mit threadlokalen Variablen.

Um eine partitionslokale Variable in einer ForEach-Schleife verwenden zu können, müssen Sie eine der Methodenüberladungen aufrufen, die zwei Typparameter erhält. Der erste Typparameter, TSource, gibt den Typ des Quellelements, der zweite Typparameter, TLocal, den Typ der partitionslokalen Variable an.

Beispiel

Im folgenden Beispiel wird die Parallel.ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>)-Überladung aufgerufen, um die Summe eines Arrays mit einer Million Elementen zu berechnen. Diese Überladung hat vier Parameter:

  • source ist die Datenquelle. Der Parameter muss IEnumerable<T> implementieren. Die Datenquelle in unserem Beispiel ist IEnumerable<Int32>-Objekt mit einer Million Mitgliedern, das von der Enumerable.Range-Methode zurückgegeben wird.

  • localInit oder die Funktion, die die partitionslokale Variable initialisiert. Diese Funktion wird einmal für jede Partition aufgerufen, in der der Parallel.ForEach-Vorgang ausgeführt wird. In unserem Beispiel wird die partitionslokale Variable auf 0 (null) initialisiert.

  • body, ein Func<T1,T2,T3,TResult>-Objekt, wird von der parallelen Schleife in jeder Iteration der Schleife aufgerufen. Die Signatur lautet Func\<TSource, ParallelLoopState, TLocal, TLocal>. Sie stellen den Code für den Delegaten bereit, und die Schleife übergibt die folgenden Eingabeparameter:

    • Das aktuelle Element von IEnumerable<T>.

    • Eine ParallelLoopState-Variable, die Sie im Code Ihres Delegaten verwenden können, um den Zustand der Schleife zu untersuchen.

    • Die partitionslokale Variable.

    Ihr Delegat gibt die partitionslokale Variable zurück, die dann an die nächste Iteration der Schleife übergeben wird, die in dieser bestimmten Partition ausgeführt wird. Jede Schleifenpartition behält eine separate Instanz dieser Variable bei.

    In dem Beispiel fügt der Delegat den Wert jeder ganzen Zahl zur partitionslokalen Variable hinzu, die einen laufenden Gesamtbetrag der Werte der ganzzahligen Elemente in dieser Partition beibehält.

  • localFinally, ein Action<TLocal>-Delegat, der von Parallel.ForEach aufgerufen wird, wenn die Schleifenvorgänge in jeder Partition abgeschlossen wurden. Die Parallel.ForEach-Methode übergibt Ihrem Action<TLocal>-Delegaten den ersten Wert der partitionslokalen Variable für diese Schleifenpartition, und Sie stellen den Code bereit, der die erforderliche Aktion zum Kombinieren des Ergebnisses von dieser Partition mit den Ergebnissen der anderen Partitionen bereitstellt. Dieser Delegat kann von mehreren Aufgaben gleichzeitig aufgerufen werden. Darum verwendet das Beispiel die Interlocked.Add(Int32, Int32)-Methode, um den Zugriff auf die Variable total zu synchronisieren. Da der Delegattyp ein Action<T>-Objekt ist, ist kein Rückgabewert vorhanden.

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

Siehe auch