Freigeben über


Einführung in PLINQ

Was ist eine parallele-Abfrage?

Language Integrated Query (LINQ) wurde in .NET Framework 3.0 eingeführt und bietet ein einheitliches Modell für die typsichere Abfrage beliebiger System.Collections.IEnumerable- oder System.Collections.Generic.IEnumerable<T>-Datenquellen. LINQ to Objects ist der Name für LINQ-Abfragen von Auflistungen im Arbeitsspeicher, z. B. List<T>, und Arrays. Dieser Artikel setzt Grundkenntnisse über LINQ voraus. Weitere Informationen finden Sie unter LINQ (Language-Integrated Query, sprachintegrierte Abfrage).

Parallel LINQ (PLINQ) ist eine parallele Implementierung des LINQ-Musters. Eine PLINQ-Abfrage entspricht weitgehend einer nicht parallelen LINQ to Objects-Abfrage. PLINQ-Abfragen werden, ebenso wie sequenzielle LINQ-Abfragen, auf alle IEnumerable- oder IEnumerable<T>-Datenquellen im Arbeitsspeicher angewendet und weisen eine verzögerte Ausführung auf, d. h. sie werden erst ausgeführt, wenn die Abfrage aufgelistet wird. Der Hauptunterschied besteht darin, dass PLINQ versucht, alle Prozessoren im System vollständig auszuschöpfen. PLINQ partitioniert hierzu die Datenquelle in Segmente und führt dann die Abfrage für jedes Segment parallel in separaten Arbeitsthreads und auf mehreren Prozessoren aus. In vielen Fällen bedeutet eine parallele Ausführung, dass die Abfrage deutlich schneller ausgeführt wird.

Durch die parallele Ausführung kann PLINQ für bestimmte Abfragen eine erheblich höhere Leistung als Legacycode erzielen. Häufig muss hierzu lediglich der AsParallel-Abfragevorgang zur Datenquelle hinzugefügt werden. Die Parallelität kann jedoch eigene Komplexitäten mit sich bringen, und nicht alle Abfragevorgänge werden in PLINQ schneller ausgeführt. Im Gegenteil, manche Abfragen werden durch die Parallelisierung sogar verlangsamt. Sie sollten daher wissen, wie sich bestimmte Aspekte, z. B. Sortierung, auf parallele Abfragen auswirken. Weitere Informationen finden Sie unter Grundlagen zur Beschleunigung in PLINQ.

HinweisHinweis

In dieser Dokumentation werden Delegaten in PLINQ mithilfe von Lambda-Ausdrücken definiert.Falls Sie nicht mit der Verwendung von Lambda-Ausdrücken in C# oder Visual Basic vertraut sind, finden Sie entsprechende Informationen unter Lambda-Ausdrücke in PLINQ und TPL.

Der Rest dieses Artikels bietet eine Übersicht über die wichtigsten PLINQ-Klassen und erläutert, wie PLINQ-Abfragen erstellt werden. Jeder Abschnitt enthält Links zu ausführlicheren Informationen und Codebeispielen.

Die ParallelEnumerable-Klasse

Die System.Linq.ParallelEnumerable-Klasse stellt nahezu die gesamte Funktionalität von PLINQ zur Verfügung. Diese Klasse sowie die restlichen Typen des System.Linq-Namespace werden in der Assembly "System.Core.dll" kompiliert. Die standardmäßigen C#- und Visual Basic-Projekte in Visual Studio verweisen auf die Assembly und importiert den Namespace.

ParallelEnumerable enthält Implementierungen aller Standardabfrageoperatoren, die von LINQ to Objects unterstützt werden, auch wenn nicht für jeden eine Parallelisierung angestrebt wird. Weitere Informationen zu LINQ finden Sie unter Einführung in LINQ.

Zusätzlich zu den Standardabfrageoperatoren enthält die ParallelEnumerable-Klasse einen Satz von Methoden, die spezielle Verhaltensweisen für die parallele Ausführung unterstützen. Diese PLINQ-spezifischen Methoden sind in der folgenden Tabelle aufgeführt.

ParallelEnumerable-Operator

Beschreibung

AsParallel

Der Einstiegspunkt für PLINQ. Gibt an, dass der Rest der Abfrage nach Möglichkeit parallelisiert werden soll.

AsSequential<TSource>

Gibt an, dass der Rest der Abfrage sequenziell, als nicht parallele LINQ-Abfrage ausgeführt werden soll.

AsOrdered

Gibt an, diese PLINQ die Reihenfolge der Quellsequenz im Rest der Abfrage oder bis zu einer Änderung der Reihenfolge, z. B. durch eine orderby-Klausel (Ordner By in Vlsual Basic), beibehalten soll.

AsUnordered<TSource>

Gibt an, dass PLINQ die Reihenfolge der Quellsequenz im Rest der Abfrage nicht beibehalten muss.

WithCancellation<TSource>

Gibt an, diese PLINQ den Zustand des bereitgestellten Abbruchtokens in regelmäßigen Abständen überwachen und die Ausführung auf Anforderung abbrechen soll.

WithDegreeOfParallelism<TSource>

Gibt die maximale Anzahl von Prozessoren an, die PLINQ zum Parallelisieren der Abfrage verwenden soll.

WithMergeOptions<TSource>

Gibt an, wie PLINQ parallele Ergebnisse im Consumerthread wieder in einer Sequenz zusammenführen soll, sofern dies möglich ist.

WithExecutionMode<TSource>

Gibt an, ob PLINQ die Abfrage parallelisieren soll, selbst wenn diese standardmäßig sequenziell ausgeführt würde.

ForAll<TSource>

Eine Multithreadenumerationsmethode, die, im Gegensatz zum Durchlaufen der Ergebnisse der Abfrage, eine parallele Verarbeitung der Ergebnisse ermöglicht, ohne dass diese zuvor im Consumerthread zusammengeführt werden.

Aggregate-Überladung

Eine spezielle Überladung für PLINQ, die eine Zwischenaggregation über lokale Threadpartitionen sowie eine abschließende Aggregationsfunktion zum Kombinieren der Ergebnisse aller Partitionen ermöglicht.

Das Opt-in-Modell

Beim Schreiben einer Abfrage entscheiden Sie sich für PLINQ, indem Sie in der Datenquelle die ParallelEnumerable.AsParallel-Erweiterungsmethode aufrufen, wie im folgenden Beispiel gezeigt.

Dim source = Enumerable.Range(1, 10000)

' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
               Where Compute(num) > 0
               Select num
var source = Enumerable.Range(1, 10000);


// Opt-in to PLINQ with AsParallel
var evenNums = from num in source.AsParallel()
               where Compute(num) > 0
               select num;

Die AsParallel-Erweiterungsmethode bindet die nachfolgenden Abfrageoperatoren, in diesem Fall, where und select, an die System.Linq.ParallelEnumerable-Implementierungen.

Ausführungsmodi

Standardmäßig wird PLINQ konservativ ausgeführt. Die PLINQ-Infrastruktur analysiert zur Laufzeit die Gesamtstruktur der Abfrage. Wenn die Abfrage voraussichtlich durch eine Parallelisierung beschleunigt werden kann, partitioniert PLINQ die Quellsequenz in Aufgaben, die gleichzeitig ausgeführt werden können. Kann eine Abfrage nicht sicher parallelisiert werden, führt PLINQ die Abfrage sequenziell aus. Wenn PLINQ die Wahl zwischen einem potenziell rechenintensiven parallelen Algorithmus und einem einfachen sequenziellen Algorithmus hat, entscheidet es sich standardmäßig für den sequenziellen Algorithmus. Mit der WithExecutionMode<TSource>-Methode und der System.Linq.ParallelExecutionMode-Enumeration können Sie PLINQ anweisen, den parallelen Algorithmus auszuwählen. Dies ist sinnvoll, wenn Sie aufgrund von Tests und Messungen wissen, dass eine bestimmte Abfrage im parallelen Modus schneller ausgeführt wird. Weitere Informationen finden Sie unter Gewusst wie: Angeben des Ausführungsmodus in PLINQ.

Grad der Parallelität

Standardmäßig verwendet PLINQ alle Prozessoren des Hostcomputers bis zu einem Maximum von 64. Mit der WithDegreeOfParallelism<TSource>-Methode können Sie PLINQ anweisen, nicht mehr als eine angegebene Anzahl von Prozessoren zu verwenden. Sie können so sicherstellen, dass andere auf dem Computer ausgeführt Prozesse eine bestimmte Menge an CPU-Zeit erhalten. Der folgende Codeausschnitt beschränkt die Abfrage auf maximal zwei Prozessoren.

Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)
            Where Compute(item) > 42
            Select item
var query = from item in source.AsParallel().WithDegreeOfParallelism(2)
            where Compute(item) > 42
            select item;

Wenn eine Abfrage eine große Mengen an nicht rechnergebundenen Aufgaben ausführt, z. B. Datei-E/A, kann es von Vorteil sein, einen höheren Grad an Parallelität als die auf dem Rechner verfügbare Anzahl von Kernen anzugeben.

Geordnete und ungeordnete parallele Abfragen

Bei einigen Abfragen muss ein Abfrageoperator Ergebnisse erzeugen, die die Reihenfolge der Quellsequenz beibehalten. PLINQ stellt für diesen Zweck den AsOrdered-Operator bereit. AsOrdered unterscheidet sich von AsSequential<TSource>. Eine AsOrdered-Sequenz wird weiterhin parallel ausgeführt, die Ergebnisse werden jedoch gepuffert und sortiert. Da die Beibehaltung der Reihenfolge in der Regel einen gewissen Mehraufwand bedeutet, wird eine AsOrdered-Sequenz ggf. langsamer verarbeitet als die standardmäßige AsUnordered<TSource>-Sequenz. Ob ein bestimmter geordneter paralleler Vorgang schneller ist als eine sequenzielle Version des Vorgangs, hängt von vielen Faktoren ab.

Im folgenden Codebeispiel wird gezeigt, wie Sie die Beibehaltung der Reihenfolge aktivieren.

        Dim evenNums = From num In numbers.AsParallel().AsOrdered()
                      Where num Mod 2 = 0
                      Select num



            evenNums = from num in numbers.AsParallel().AsOrdered()
                       where num % 2 == 0
                       select num;


Weitere Informationen finden Sie unter Beibehaltung der Reihenfolge in PLINQ.

Parallele undsequenzielle Abfragen

Einige Vorgänge erfordern, dass die Quelldaten sequenziell übergeben werden. Die ParallelEnumerable-Abfrageoperatoren stellen automatisch den sequenziellen Modus wieder her, wenn dies erforderlich ist. Für benutzerdefinierte Abfrageoperatoren und Benutzerdelegaten, die eine sequenzielle Ausführung erfordern, stellt PLINQ die AsSequential<TSource>-Methode bereit. Bei Verwendung von AsSequential<TSource> werden alle nachfolgenden Operatoren in der Abfrage sequenziell ausgeführt, bis AsParallel erneut aufgerufen wird. Weitere Informationen finden Sie unter Gewusst wie: Kombinieren von parallelen und sequenziellen LINQ-Abfragen.

Optionen für das Zusammenführen von Abfrageergebnissen

Wenn eine PLINQ-Abfrage parallel ausgeführt wird, müssen die Ergebnisse aller Arbeitsthreads wieder im Hauptthread zusammengeführt werden, damit sie in einerforeach-Schleife (For Each in Visual Basic) verwendet oder in eine Liste bzw. ein Array eingefügt werden können. In einigen Fällen ist es hilfreich, eine bestimmte Art von Zusammenführung anzugeben, z. B. um Ergebnisse schneller zu erzeugen. Zu diesem Zweck unterstützt PLINQ die WithMergeOptions<TSource>-Methode und die ParallelMergeOptions-Enumeration. Weitere Informationen finden Sie unter Zusammenführungsoptionen in PLINQ.

Der ForAll-Operator

In sequenziellen LINQ-Abfragen wird die Ausführung verzögert, bis die Abfrage entweder in einer foreach-Schleife (For Each in Visual Basic) oder durch das Aufrufen einer Methode, z. B. ToList<TSource>, ToTSource> oder ToDictionary, aufgelistet wird. In PLINQ können Sie zudem foreach verwenden, um die Abfrage auszuführen und die Ergebnisse zu durchlaufen. foreach selbst wird jedoch nicht parallel ausgeführt, sodass die Ausgabe aller parallelen Aufgaben wieder in dem Thread, in dem die Schleife ausgeführt wird, zusammengeführt werden muss. In PLINQ können Sie foreach verwenden, wenn Sie die abschließende Reihenfolge der Abfrageergebnisse beibehalten möchten oder wenn Sie die Ergebnisse seriell verarbeiten, z. B. wenn Sie für jedes Element Console.WriteLine aufrufen. Wenn die Beibehaltung der Reihenfolge nicht erforderlich ist und die Verarbeitung der Ergebnisse selbst parallelisiert werden kann, verwenden Sie die ForAll<TSource>-Methode, um die Ausführung der PLINQ-Abfrage zu beschleunigen. ForAll<TSource> führt die abschließende Zusammenführung nicht aus. Im folgenden Codebeispiel wird die Verwendung der ForAll<TSource>-Methode veranschaulicht. System.Collections.Concurrent.ConcurrentBag<T> wird hier verwendet, da sie für mehrere Threads optimiert ist, die gleichzeitig Elemente hinzufügen, ohne Elemente zu entfernen.

Dim nums = Enumerable.Range(10, 10000)
Dim query = From num In nums.AsParallel()
            Where num Mod 10 = 0
            Select num

' Process the results as each thread completes
' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))

var nums = Enumerable.Range(10, 10000);


var query = from num in nums.AsParallel()
            where num % 10 == 0
            select num;

// Process the results as each thread completes
// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll((e) => concurrentBag.Add(Compute(e)));

Die folgende Abbildung zeigt den Unterschied zwischen foreach und ForAll<TSource> hinsichtlich der Abfrageausführung.

ForAll und ForEach

Abbruch

PLINQ ist in die Abbruchtypen in .NET Framework 4 integriert. (Weitere Informationen finden Sie unter Abbruch.) Daher können PLINQ-Abfragen im Gegensatz zu sequenziellen LINQ to Objects-Abfragen abgebrochen werden. Um eine abbrechbare PLINQ-Abfrage zu erstellen, verwenden Sie den WithCancellation<TSource>-Operator in der Abfrage, und stellen Sie eine CancellationToken-Instanz als Argument bereit. Wenn die IsCancellationRequested-Eigenschaft im Token auf true festgelegt ist, erkennt PLINQ dies. Die Verarbeitung wird in diesem Fall in allen Threads abgebrochen, und eine OperationCanceledException wird ausgelöst.

Es ist möglich, dass eine PLINQ-Abfrage nach dem Festlegen des Abbruchtokens weiterhin einige Elemente verarbeitet.

Um kürzere Reaktionszeiten zu erzielen, können Sie auch auf Abbruchanforderungen in Benutzerdelegaten mit langer Laufzeit reagieren. Weitere Informationen finden Sie unter Gewusst wie: Abbrechen einer PLINQ-Abfrage.

Ausnahmen

Bei der Ausführung einer PLINQ-Abfrage können mehrere Ausnahmen von verschiedenen Threads gleichzeitig ausgelöst werden. Zudem kann sich der Code für die Behandlung einer Ausnahme in einem anderen Thread befinden als der Code, der die Ausnahme ausgelöst hat. PLINQ kapselt alle Ausnahmen, die von einer Abfrage ausgelöst wurden, mithilfe des AggregateException-Typs und marshallt diese Ausnahmen zurück an den aufrufenden Thread. Im aufrufenden Thread ist nur ein try/catch-Block erforderlich. Sie können jedoch alle Ausnahmen durchlaufen, die in AggregateException gekapselt sind, und die Ausnahmen erfassen, die Sie sicher beheben können. In seltenen Fällen können Ausnahmen ausgelöst werden, die nicht in einer AggregateException eingeschlossen sind. ThreadAbortExceptions sind ebenfalls nicht eingeschlossen.

Wenn Ausnahmen mittels Bubbling wieder an den Verbindungsthread übergeben werden können, ist es möglich, dass eine Abfrage nach dem Auslösen der Ausnahme weiterhin einige Elemente verarbeitet.

Weitere Informationen finden Sie unter Gewusst wie: Behandeln von Ausnahmen in einer PLINQ-Abfrage.

Benutzerdefinierte Partitionierer

Sie können die Abfrageleistung teilweise erhöhen, indem Sie einen benutzerdefinierten Partitionierer schreiben, der bestimmte Merkmale der Quelldaten nutzt. In der Abfrage ist der benutzerdefinierte Partitionierer das auflistbare Objekt, das abgefragt wird.

    [Visual Basic]
    Dim arr(10000) As Integer
    Dim partitioner = New MyArrayPartitioner(Of Integer)(arr)
    Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))
    [C#]
    int[] arr= ...;
    Partitioner<int> partitioner = newMyArrayPartitioner<int>(arr);
    var q = partitioner.AsParallel().Select(x => SomeFunction(x));

PLINQ unterstützt eine feste Anzahl von Partitionen (auch wenn die Daten während der Laufzeit diesen Partitionen dynamisch neu zugeordnet werden können, um einen Lastenausgleich zu gewährleisten). For und ForEach unterstützen nur die dynamische Partitionierung, d. h. die Anzahl der Partitionen ändert sich zur Laufzeit. Weitere Informationen finden Sie unter Benutzerdefinierte Partitionierer für PLINQ und TPL.

Messen der PLINQ-Leistung

In vielen Fällen kann eine Abfrage parallelisiert werden, der mit dem Einrichten der parallelen Abfrage verbundene Mehraufwand überwiegt jedoch die erzielte Leistungssteigerung. Wenn eine Abfrage nur wenige Berechnung ausführt oder wenn die Datenquelle klein ist, ist eine PLINQ-Abfrage möglicherweise langsamer als ein sequenzielle LINQ to Objects-Abfrage. Mithilfe des Parallel Performance Analyzer in Visual Studio Team Server können Sie die Leistung verschiedener Abfragen vergleichen, Verarbeitungsengpässe suchen und bestimmen, ob die Abfrage parallel oder sequenziell ausgeführt wird. Weitere Informationen finden Sie unter Parallelitätsschnellansicht und Gewusst wie: Messen der Leistung von PLINQ-Abfragen.

Siehe auch

Konzepte

Paralleles LINQ (PLINQ)

Grundlagen zur Beschleunigung in PLINQ