Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In veel gevallen kan PLINQ aanzienlijke prestatieverbeteringen bieden ten opzichte van sequentiële LINQ naar objectenquery's. Het werk van het parallelliseren van de queryuitvoering introduceert echter complexiteit die kan leiden tot problemen die, in sequentiële code, niet zo gebruikelijk zijn of helemaal niet worden aangetroffen. In dit onderwerp vindt u enkele procedures om te voorkomen wanneer u PLINQ-query's schrijft.
Ga er niet van uit dat parallel altijd sneller is
Parallelisatie veroorzaakt soms dat een PLINQ-query langzamer wordt uitgevoerd dan de equivalente LINQ naar Objecten. De vuistregel is dat query's met weinig bronelementen en snelle gebruikersdelegenten waarschijnlijk niet veel sneller zullen worden. Omdat er echter veel factoren bij de prestaties betrokken zijn, raden we u aan werkelijke resultaten te meten voordat u besluit of u PLINQ wilt gebruiken. Voor meer informatie, zie Het begrijpen van snelheidstoename in PLINQ.
Vermijd schrijven naar gedeelde geheugenlocaties
In sequentiële code is het niet ongebruikelijk om statische variabelen of klassevelden te lezen of te schrijven. Wanneer meerdere threads echter gelijktijdig toegang hebben tot dergelijke variabelen, is er een groot potentieel voor racevoorwaarden. Hoewel u vergrendelingen kunt gebruiken om de toegang tot de variabele te synchroniseren, kunnen de kosten van synchronisatie de prestaties schaden. Daarom wordt u aangeraden de toegang tot de gedeelde status in een PLINQ-query zoveel mogelijk te vermijden of te beperken.
Overparallellisatie voorkomen
Met behulp van de AsParallel methode worden de overheadkosten in rekening gebracht voor het partitioneren van de bronverzameling en het synchroniseren van de werkthreads. De voordelen van parallelle uitvoering worden verder beperkt door het aantal processors op de computer. Er is geen versnelling te behalen door meerdere compute-gebonden threads uit te voeren op slechts één processor. Daarom moet u voorzichtig zijn om een query niet overmatig te parallelliseren.
Het meest voorkomende scenario waarin over-parallelisatie kan optreden, doet zich voor bij geneste query's, zoals wordt weergegeven in het volgende fragment.
var q = from cust in customers.AsParallel()
from order in cust.Orders.AsParallel()
where order.OrderDate > date
select new { cust, order };
Dim q = From cust In customers.AsParallel()
From order In cust.Orders.AsParallel()
Where order.OrderDate > aDate
Select New With {cust, order}
In dit geval kunt u het beste alleen de buitenste gegevensbron (klanten) parallelliseren, tenzij een of meer van de volgende voorwaarden van toepassing zijn:
De binnenste gegevensbron (cust.Orders) staat bekend om zijn grote lengte.
U voert een dure berekening uit op elke order. (De bewerking die in het voorbeeld wordt weergegeven, is niet duur.)
Het is bekend dat het doelsysteem voldoende processors heeft om het aantal threads te verwerken dat wordt geproduceerd door de query op
cust.Ordersparallel te maken.
In alle gevallen kunt u de optimale queryvorm het beste testen en meten. Zie Procedure: PLINQ-queryprestaties meten voor meer informatie.
Vermijd aanroepen naar niet-threadveilige methoden
Schrijven naar niet-threadveilige exemplaarmethoden van een PLINQ-query kan leiden tot beschadiging van gegevens die mogelijk of niet worden gedetecteerd in uw programma. Het kan ook leiden tot uitzonderingen. In het volgende voorbeeld proberen meerdere threads tegelijkertijd de FileStream.Write methode aan te roepen, die niet wordt ondersteund door de klasse.
Dim fs As FileStream = File.OpenWrite(…)
a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(Sub(x) fs.Write(x))
FileStream fs = File.OpenWrite(...);
a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(x => fs.Write(x));
Aanroepen beperken tot threadveilige methoden
De meeste statische methoden in .NET zijn thread-veilig en kunnen gelijktijdig vanuit meerdere threads worden aangeroepen. Zelfs in deze gevallen kan de betrokken synchronisatie echter leiden tot een aanzienlijke vertraging in de query.
Opmerking
U kunt dit zelf testen door enkele aanroepen aan uw queries toe te voegen door WriteLine in te voegen. Hoewel deze methode wordt gebruikt in de documentatievoorbeelden voor demonstratiedoeleinden, moet u deze niet gebruiken in PLINQ-query's.
Vermijd onnodige bestelbewerkingen
Wanneer PLINQ een query parallel uitvoert, wordt de bronreeks verdeeld in partities waarop gelijktijdig op meerdere threads kan worden uitgevoerd. Standaard is de volgorde waarin de partities worden verwerkt en de resultaten worden geleverd niet voorspelbaar (met uitzondering van operators zoals OrderBy). U kunt PLINQ instrueren om de volgorde van elke bronvolgorde te behouden, maar dit heeft een negatieve invloed op de prestaties. Waar mogelijk is het best om query's zodanig te structuren dat ze niet afhankelijk zijn van het behoud van de bestelling. Zie Orderbehoud in PLINQ voor meer informatie.
Geef De voorkeur aan ForAll aan ForEach wanneer het mogelijk is
Hoewel PLINQ een query uitvoert op meerdere threads, moeten de queryresultaten, als u de resultaten in een foreach-lus (For Each in Visual Basic) gebruikt, worden samengevoegd in één thread en serieel worden benaderd door de enumerator. In sommige gevallen is dit onvermijdelijk; Gebruik echter, indien mogelijk, de ForAll methode om elke thread in staat te stellen zijn eigen resultaten uit te voeren, bijvoorbeeld door te schrijven naar een thread-safe-verzameling, zoals System.Collections.Concurrent.ConcurrentBag<T>.
Hetzelfde probleem is van toepassing op Parallel.ForEach. Met andere woorden, source.AsParallel().Where().ForAll(...) zou sterk de voorkeur moeten krijgen boven Parallel.ForEach(source.AsParallel().Where(), ...).
Houd rekening met kwesties met thread-affiniteit
Sommige technologieën, zoals COM-interoperabiliteit voor STA-onderdelen (Single Threaded Apartment), Windows Forms en Windows Presentation Foundation (WPF), leggen threadaffiniteitsbeperkingen op waarvoor code moet worden uitgevoerd op een specifieke thread. In Zowel Windows Forms als WPF kan een besturingselement bijvoorbeeld alleen worden geopend op de thread waarop het is gemaakt. Als u probeert toegang te krijgen tot de gedeelde status van een Windows Forms-besturingselement in een PLINQ-query, wordt er een uitzondering gegenereerd als u in het foutopsporingsprogramma werkt. (Deze instelling kan worden uitgeschakeld.) Als uw query echter wordt gebruikt in de UI-thread, hebt u toegang tot het besturingselement vanuit de foreach lus waarmee de queryresultaten worden opgesomd, omdat die code wordt uitgevoerd op slechts één thread.
Stel niet dat iteraties van ForEach, For en ForAll altijd parallel worden uitgevoerd
Het is belangrijk om in gedachten te houden dat afzonderlijke iteraties in een Parallel.For, Parallel.ForEachof ForAll lus kunnen worden uitgevoerd, maar niet parallel hoeven uit te voeren. Daarom moet u voorkomen dat u code schrijft die afhankelijk is van juistheid van parallelle uitvoering van iteraties of van de uitvoering van iteraties in een bepaalde volgorde.
Deze code zal bijvoorbeeld waarschijnlijk vastlopen.
Dim mre = New ManualResetEventSlim()
Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll(Sub(j)
If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Wait()
End If
End Sub) ' deadlocks
ManualResetEventSlim mre = new ManualResetEventSlim();
Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Set();
}
else
{
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Wait();
}
}); //deadlocks
In dit voorbeeld wordt met één iteratie een gebeurtenis ingesteld en wachten alle andere iteraties op de gebeurtenis. Geen van de wachtende iteraties kan worden voltooid totdat de iteratie voor het instellen van het evenement is voltooid. Het is echter mogelijk dat de wachtende iteraties alle threads blokkeren die worden gebruikt om de parallelle lus uit te voeren, voordat de iteratie die het evenement instelt de kans heeft gehad om uitgevoerd te worden. Dit resulteert in een impasse: de iteratie voor gebeurtenisinstelling wordt nooit uitgevoerd en de wachtende iteraties worden nooit geactiveerd.
In het bijzonder moet een iteratie van een parallelle lus nooit wachten op een andere iteratie van de lus om vooruitgang te boeken. Als de parallelle lus besluit om de iteraties opeenvolgend te plannen, maar in de omgekeerde volgorde, treedt er een impasse op.