Vorgehensweise: Schreiben einer Parallel.For-Schleife mit threadlokalen Variablen

Dieses Beispiel zeigt, wie Sie threadlokale Variablen verwenden, um den Status in jeder separaten Aufgabe zu speichern und abzurufen, die von einer For-Schleife erstellt wird. Durch die Verwendung von threadlokalen Daten können Sie den mit der Synchronisierung einer großen Anzahl von Zugriffen auf einen Freigabezustand verbundenen Mehraufwand vermeiden. Statt an eine freigegebene Ressourcen in jeder Iteration zu schreiben, berechnen und speichern Sie den Wert, bis alle Iterationen für die Aufgabe abgeschlossen sind. Sie können dann das endgültige Ergebnis einmal an die freigegebene Ressource schreiben oder sie an eine andere Methoden übergeben.

Beispiel

Im folgenden Beispiel wird die For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>)-Methode aufgerufen, um die Summe der Werte in einem Array zu berechnen, das eine Million Elemente enthält. Der Wert für jedes Element entspricht dem Index.

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

Die ersten zwei Parameter jeder For-Methode geben die Anfangs- und Enditerationswerte an. In dieser Überladung der Methode ist der dritte Parameter die Stelle, an der Sie Ihren lokalen Zustand initialisieren. In diesem Kontext bedeutet lokaler Zustand eine Variable, deren Lebensdauer sich von dem Zeitpunkt gerade vor der ersten Iteration der Schleife im aktuellen Thread bis zu dem Zeitpunkt gerade nach der letzten Iteration erstreckt.

Der dritte Parameter ist vom Typ Func<TResult>, wobei TResult der Variablentyp ist, der den threadlokalen Zustand speichert. Der Typ wird von dem generischen Typargument beim Aufrufen der generischen For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>)-Methode definiert, in diesem Fall Int64. Das Typargument teilt dem Computer den Typ der temporären Variable mit, die verwendet wird, um den threadlokalen Zustand zu speichern. In diesem Beispiel initialisiert der Ausdruck () => 0 (oder Function() 0 in Visual Basic) die lokale Threadvariable auf Null. Wenn das generische Typargument ein Referenztyp oder ein benutzerdefinierter Werttyp ist, würde der Ausdruck wie folgt aussehen:

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

Der vierte Parameter definiert die Schleifenlogik. Er muss ein Delegat oder Lambdaausdruck sein, dessen Signatur Func<int, ParallelLoopState, long, long> in C# oder Func(Of Integer, ParallelLoopState, Long, Long) in Visual Basic ist. Der erste Parameter ist der Wert des Schleifenzählers für diese Iteration der Schleife. Der zweite ist ein ParallelLoopState-Objekt, das verwendet werden kann, um die Schleife zu unterbrechen. Dieses Objekt wird von der Parallel-Klasse für jedes Auftreten der Schleife bereitgestellt. Der dritte Parameter ist die lokale Threadvariable. Der letzte Parameter ist der Rückgabetyp. In diesem Fall ist der Typ Int64, weil wir diesen Typ im For-Typargument angegeben haben. Diese Variable hat den Namen subtotal und wird vom Lambda-Ausdruck zurückgegeben. Die Rückgabewert wird verwendet, um subtotal in jeder folgenden Iteration der Schleife zu initialisieren. Sie können sich diesen letzten Parameter auch als einen Wert vorstellen, der an jede Iteration und dann an den localFinally-Delegaten übergeben wird, wenn die letzte Iteration abgeschlossen ist.

Der fünfte Parameter definiert die Methode, die einmal aufgerufen wird, nachdem alle Iterationen in einem bestimmten Thread abgeschlossen wurden. Der Typ des Eingabearguments entspricht erneut dem Typargument der For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>)-Methode und dem vom Text-Lambda-Ausdruck zurückgegebenen Typ. In diesem Beispiel wird der Wert zu einer Variable im Gültigkeitsbereich einer Klasse auf eine threadsichere Weise hinzugefügt, indem die Interlocked.Add-Methode aufgerufen wird. Durch die Verwendung einer lokalen Threadvariable haben wir das Schreiben an diese Klassenvariable in jeder Iteration der Schleife vermieden.

Weitere Informationen zur Verwendung von Lambdaausdrücken finden Sie unter Lambdaausdrücke in PLINQ und TPL.

Siehe auch