Delen via


Inleiding tot PLINQ

Parallel LINQ (PLINQ) is een parallelle implementatie van het LINQ-patroon (Language-Integrated Query). PLINQ implementeert de volledige set linQ-standaardqueryoperators als extensiemethoden voor de System.Linq naamruimte en bevat extra operators voor parallelle bewerkingen. PLINQ combineert de eenvoud en leesbaarheid van LINQ-syntaxis met de kracht van parallelle programmering.

Tip

Als u niet bekend bent met LINQ, beschikt deze over een geïntegreerd model voor het opvragen van een enumerable gegevensbron op een typeveilige manier. LINQ naar objecten is de naam voor LINQ-query's die worden uitgevoerd op in-memory verzamelingen, zoals List<T> en matrices. In dit artikel wordt ervan uitgegaan dat u basiskennis hebt van LINQ. Zie Language-Integrated Query (LINQ) voor meer informatie.

Wat is een parallelle query?

Een PLINQ-query lijkt op veel manieren op een niet-parallelle LINQ naar objectenquery. PLINQ-query's, net zoals sequentiële LINQ-query's, werken op een willekeurige in-memory IEnumerable of IEnumerable<T> gegevensbron en hebben de uitvoering uitgesteld. Dit betekent dat ze pas worden uitgevoerd als de query is geïnventariseerd. Het belangrijkste verschil is dat PLINQ probeert volledig gebruik te maken van alle processors op het systeem. Dit doet u door de gegevensbron te partitioneren in segmenten en vervolgens de query uit te voeren op elk segment op afzonderlijke werkthreads parallel op meerdere processors. In veel gevallen betekent parallelle uitvoering dat de query aanzienlijk sneller wordt uitgevoerd.

Door parallelle uitvoering kan PLINQ aanzienlijke prestatieverbeteringen bereiken ten opzichte van verouderde code voor bepaalde soorten query's, vaak alleen door de AsParallel querybewerking toe te voegen aan de gegevensbron. Parallellisme kan echter eigen complexiteit introduceren en niet alle querybewerkingen worden sneller uitgevoerd in PLINQ. Parallellisatie vertraagt zelfs bepaalde query's. Daarom moet u begrijpen hoe problemen zoals ordenen van invloed zijn op parallelle query's. Zie Inzicht in snelheid in PLINQ voor meer informatie.

Notitie

In deze documentatie worden lambda-expressies gebruikt om gedelegeerden in PLINQ te definiëren. Als u niet bekend bent met lambda-expressies in C# of Visual Basic, raadpleegt u Lambda-expressies in PLINQ en TPL.

In de rest van dit artikel vindt u een overzicht van de belangrijkste PLINQ-klassen en wordt besproken hoe u PLINQ-query's maakt. Elke sectie bevat koppelingen naar meer gedetailleerde informatie en codevoorbeelden.

De klasse ParallelEnumerable

De System.Linq.ParallelEnumerable klasse toont bijna alle functionaliteit van PLINQ. Het en de rest van de System.Linq naamruimtetypen worden gecompileerd in de System.Core.dll assembly. De standaard C#- en Visual Basic-projecten in Visual Studio verwijzen beide naar de assembly en importeren de naamruimte.

ParallelEnumerable bevat implementaties van alle standaardqueryoperators die LINQ naar Objecten ondersteunt, hoewel er niet wordt geprobeerd om elke operator te parallelliseren. Zie Inleiding tot LINQ (C#) en Inleiding tot LINQ (Visual Basic) als u niet bekend bent met LINQ.

Naast de standaardqueryoperators bevat de ParallelEnumerable klasse een set methoden waarmee gedrag mogelijk is dat specifiek is voor parallelle uitvoering. Deze PLINQ-specifieke methoden worden vermeld in de volgende tabel.

ParallelEnumerable Operator Beschrijving
AsParallel Het toegangspunt voor PLINQ. Hiermee geeft u op dat de rest van de query moet worden geparallelliseerd, indien mogelijk.
AsSequential Hiermee geeft u op dat de rest van de query opeenvolgend moet worden uitgevoerd als een niet-parallelle LINQ-query.
AsOrdered Hiermee geeft u op dat PLINQ de volgorde van de bronvolgorde voor de rest van de query moet behouden, of totdat de volgorde is gewijzigd, bijvoorbeeld door het gebruik van een orderby-component (Order By in Visual Basic).
AsUnordered Hiermee geeft u op dat PLINQ voor de rest van de query niet is vereist om de volgorde van de bronreeks te behouden.
WithCancellation Hiermee geeft u op dat PLINQ periodiek de status van het opgegeven annuleringstoken moet controleren en de uitvoering moet annuleren als dit wordt aangevraagd.
WithDegreeOfParallelism Hiermee geeft u het maximum aantal processors op dat PLINQ moet gebruiken om de query te parallelliseren.
WithMergeOptions Biedt een hint over hoe PLINQ, indien mogelijk, parallelle resultaten weer samenvoegen in slechts één reeks op de verbruikende thread.
WithExecutionMode Hiermee geeft u op of PLINQ de query moet parallelliseren, zelfs wanneer het standaardgedrag sequentieel moet worden uitgevoerd.
ForAll Een multithreaded opsommingsmethode die, in tegenstelling tot het herhalen van de resultaten van de query, in staat stelt om resultaten parallel te verwerken zonder eerst opnieuw samen te voegen aan de consumententhread.
Aggregate Overbelasting Een overbelasting die uniek is voor PLINQ en tussenliggende aggregatie mogelijk maakt via thread-lokale partities, plus een uiteindelijke aggregatiefunctie om de resultaten van alle partities te combineren.

Het aanmeldingsmodel

Wanneer u een query schrijft, meldt u zich aan bij PLINQ door de ParallelEnumerable.AsParallel extensiemethode voor de gegevensbron aan te roepen, zoals wordt weergegeven in het volgende voorbeeld.

var source = Enumerable.Range(1, 10000);

// Opt in to PLINQ with AsParallel.
var evenNums = from num in source.AsParallel()
               where num % 2 == 0
               select num;
Console.WriteLine("{0} even numbers out of {1} total",
                  evenNums.Count(), source.Count());
// The example displays the following output:
//       5000 even numbers out of 10000 total
Dim source = Enumerable.Range(1, 10000)

' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
               Where num Mod 2 = 0
               Select num
Console.WriteLine("{0} even numbers out of {1} total",
                  evenNums.Count(), source.Count())
' The example displays the following output:
'       5000 even numbers out of 10000 total

De AsParallel extensiemethode verbindt de volgende queryoperators, in dit geval where en select, met de System.Linq.ParallelEnumerable implementaties.

Uitvoeringsmodi

PLINQ is standaard conservatief. Tijdens runtime analyseert de PLINQ-infrastructuur de algehele structuur van de query. Als de query waarschijnlijk versnellingen oplevert door parallelle uitvoering, partitioneert PLINQ de bronreeks in taken die gelijktijdig kunnen worden uitgevoerd. Als het niet veilig is om een query te parallelliseren, voert PLINQ de query alleen opeenvolgend uit. Als PLINQ een keuze heeft tussen een potentieel duur parallel algoritme of een goedkoop sequentiële algoritme, wordt standaard het sequentiële algoritme gekozen. U kunt de WithExecutionMode methode en de System.Linq.ParallelExecutionMode opsomming gebruiken om PLINQ te instrueren om het parallelle algoritme te selecteren. Dit is handig als u weet door te testen en meten dat een bepaalde query sneller parallel wordt uitgevoerd. Zie Procedure: De uitvoeringsmodus opgeven in PLINQ voor meer informatie.

Mate van parallelle uitvoering

PLINQ gebruikt standaard alle processors op de hostcomputer. U kunt PLINQ instrueren om niet meer dan een opgegeven aantal processors te gebruiken met behulp van de WithDegreeOfParallelism methode. Dit is handig als u ervoor wilt zorgen dat andere processen die op de computer worden uitgevoerd, een bepaalde hoeveelheid CPU-tijd ontvangen. Het volgende codefragment beperkt de query tot maximaal twee processors.

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

In gevallen waarin een query een aanzienlijke hoeveelheid niet-rekengebonden werk uitvoert, zoals bestands-I/O, kan het nuttig zijn om een mate van parallelle uitvoering op te geven die groter is dan het aantal kernen op de computer.

Geordende versus niet-geordende parallelle query's

In sommige query's moet een queryoperator resultaten produceren die de volgorde van de bronvolgorde behouden. PLINQ biedt de AsOrdered operator voor dit doel. AsOrdered verschilt van AsSequential. Een AsOrdered reeks wordt nog steeds parallel verwerkt, maar de resultaten worden gebufferd en gesorteerd. Omdat het bewaren van bestellingen meestal extra werk omvat, kan een AsOrdered reeks langzamer worden verwerkt dan de standaardvolgorde AsUnordered . Of een bepaalde geordende parallelle bewerking sneller is dan een sequentiële versie van de bewerking, is afhankelijk van veel factoren.

In het volgende codevoorbeeld ziet u hoe u zich kunt aanmelden om het behoud te orden.

var evenNums =
    from num in numbers.AsParallel().AsOrdered()
    where num % 2 == 0
    select num;
Dim evenNums = From num In numbers.AsParallel().AsOrdered()
               Where num Mod 2 = 0
               Select num


Zie Orderbehoud in PLINQ voor meer informatie.

Parallelle versus sequentiële query's

Voor sommige bewerkingen moeten de brongegevens op een opeenvolgende manier worden geleverd. De ParallelEnumerable queryoperators keren automatisch terug naar de sequentiële modus wanneer deze is vereist. Voor door de gebruiker gedefinieerde queryoperators en gebruikersdelegenten waarvoor sequentiële uitvoering is vereist, biedt PLINQ de AsSequential methode. Wanneer u gebruikt AsSequential, worden alle volgende operators in de query opeenvolgend uitgevoerd totdat AsParallel deze opnieuw wordt aangeroepen. Zie Voor meer informatie: Parallelle en sequentiële LINQ-query's combineren.

Opties voor het samenvoegen van queryresultaten

Wanneer een PLINQ-query parallel wordt uitgevoerd, moeten de resultaten van elke werkrolthread weer worden samengevoegd naar de hoofdthread voor gebruik door een foreach lus (For Each in Visual Basic) of invoeging in een lijst of matrix. In sommige gevallen kan het nuttig zijn om een bepaald soort samenvoegbewerking op te geven, bijvoorbeeld om sneller resultaten te produceren. Hiervoor ondersteunt PLINQ de WithMergeOptions methode en de ParallelMergeOptions opsomming. Zie Opties voor samenvoegen in PLINQ voor meer informatie.

De forAll-operator

In sequentiële LINQ-query's wordt de uitvoering uitgesteld totdat de query is geïnventariseerd in een foreach (For Each in Visual Basic)-lus of door een methode zoals ToList , ToArray of ToDictionary. In PLINQ kunt u ook de foreach query uitvoeren en de resultaten herhalen. foreach Zelf wordt echter niet parallel uitgevoerd en daarom is vereist dat de uitvoer van alle parallelle taken weer wordt samengevoegd in de thread waarop de lus wordt uitgevoerd. In PLINQ kunt u gebruiken foreach wanneer u de uiteindelijke volgorde van de queryresultaten moet behouden en ook wanneer u de resultaten op een seriële manier verwerkt, bijvoorbeeld wanneer u voor elk element aanroept Console.WriteLine . Gebruik de ForAll methode om een PLINQ-query uit te voeren voor snellere uitvoering van query's wanneer het bewaren van orders niet vereist is en wanneer de verwerking van de resultaten zelf kan worden geparallelliseerd. ForAll voert deze laatste samenvoegingsstap niet uit. In het volgende codevoorbeeld ziet u hoe u de ForAll methode gebruikt. System.Collections.Concurrent.ConcurrentBag<T> wordt hier gebruikt omdat deze is geoptimaliseerd voor meerdere threads die gelijktijdig worden toegevoegd zonder items te verwijderen.

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)));
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)))

In de volgende afbeelding ziet u het verschil tussen foreach en ForAll met betrekking tot de uitvoering van query's.

ForAll vs. ForEach

Opzegging

PLINQ is geïntegreerd met de annuleringstypen in .NET. (Zie voor meer informatie Annulering in beheerde threads.) In tegenstelling tot sequentiële LINQ naar objectenquery's, kunnen PLINQ-query's daarom worden geannuleerd. Als u een annuleerbare PLINQ-query wilt maken, gebruikt u de WithCancellation operator voor de query en geeft u een CancellationToken exemplaar op als het argument. Wanneer de IsCancellationRequested eigenschap op het token is ingesteld op true, ziet PLINQ deze, stopt de verwerking op alle threads en genereert een OperationCanceledException.

Het is mogelijk dat een PLINQ-query enkele elementen blijft verwerken nadat het annuleringstoken is ingesteld.

Voor een grotere reactiesnelheid kunt u ook reageren op annuleringsaanvragen in langlopende gebruikersdelegen. Zie Procedure: Een PLINQ-query annuleren voor meer informatie.

Uitzonderingen

Wanneer een PLINQ-query wordt uitgevoerd, kunnen er meerdere uitzonderingen tegelijk worden gegenereerd vanuit verschillende threads. Bovendien kan de code voor het afhandelen van de uitzondering zich op een andere thread bevinden dan de code die de uitzondering heeft veroorzaakt. PLINQ gebruikt het AggregateException type om alle uitzonderingen in te kapselen die zijn gegenereerd door een query, en marshal deze uitzonderingen terug naar de aanroepende thread. In de aanroepende thread is slechts één try-catchblok vereist. U kunt echter alle uitzonderingen herhalen die zijn ingekapseld in de AggregateException uitzonderingen en vangen die u veilig kunt herstellen. In zeldzame gevallen kunnen sommige uitzonderingen worden gegenereerd die niet zijn verpakt in een AggregateException, en ThreadAbortExceptions worden ook niet verpakt.

Wanneer uitzonderingen zijn toegestaan om terug te bellen naar de samenvoegingsthread, is het mogelijk dat een query bepaalde items blijft verwerken nadat de uitzondering is gegenereerd.

Zie Procedures voor meer informatie: Uitzonderingen verwerken in een PLINQ-query.

Aangepaste partitioneerfuncties

In sommige gevallen kunt u de queryprestaties verbeteren door een aangepaste partitioner te schrijven die gebruikmaakt van een bepaald kenmerk van de brongegevens. In de query is de aangepaste partitioner zelf het opsommingsobject waarop een query wordt uitgevoerd.

int[] arr = new int[9999];
Partitioner<int> partitioner = new MyArrayPartitioner<int>(arr);
var query = partitioner.AsParallel().Select(SomeFunction);
Dim arr(10000) As Integer
Dim partitioner As Partitioner(Of Integer) = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))

PLINQ ondersteunt een vast aantal partities (hoewel gegevens mogelijk dynamisch opnieuw worden toegewezen aan deze partities tijdens runtime voor taakverdeling.) For en ForEach ondersteunt alleen dynamische partitionering, wat betekent dat het aantal partities tijdens runtime verandert. Zie Aangepaste partities voor PLINQ en TPL voor meer informatie.

Prestaties van PLINQ meten

In veel gevallen kan een query worden geparallelliseerd, maar de overhead van het instellen van de parallelle query weegt op tegen het prestatievoordeel dat is opgedaan. Als een query niet veel berekeningen uitvoert of als de gegevensbron klein is, kan een PLINQ-query langzamer zijn dan een opeenvolgende LINQ naar objectenquery. U kunt Parallel Performance Analyzer in Visual Studio Team Server gebruiken om de prestaties van verschillende query's te vergelijken, knelpunten te vinden en te bepalen of uw query parallel of opeenvolgend wordt uitgevoerd. Zie Gelijktijdigheid visualiseren en procedures voor meer informatie: PLINQ-queryprestaties meten.

Zie ook