Megosztás a következőn keresztül:


A PLINQ bemutatása

A párhuzamos LINQ (PLINQ) a Language-Integrated Query (LINQ) minta párhuzamos implementációja. A PLINQ a LINQ standard lekérdezési operátorok teljes készletét alkalmazza a System.Linq névtér bővítménymeteként, és további operátorokkal is rendelkezik a párhuzamos műveletekhez. A PLINQ egyesíti a LINQ szintaxis egyszerűségét és olvashatóságát a párhuzamos programozás erejével.

Tipp.

Ha nem ismeri a LINQ-t, az egységes modellt tartalmaz a számba vett adatforrások típusbiztos lekérdezéséhez. A LINQ to Objects azoknak a LINQ-lekérdezéseknek a neve, amelyek memóriabeli gyűjteményeken, például List<T> tömbökön futnak. Ez a cikk feltételezi, hogy alapszintű ismerete van a LINQ-ról. További információ: Language-Integrated Query (LINQ).

Mi az a párhuzamos lekérdezés?

A PLINQ-lekérdezések sokféleképpen hasonlítanak az Objektumokkal való nem párhuzamos LINQ-lekérdezésekhez. A PLINQ-lekérdezések, akárcsak a szekvenciális LINQ-lekérdezések, bármilyen memóriában IEnumerable vagy IEnumerable<T> adatforrásban működnek, és halasztott végrehajtással rendelkeznek, ami azt jelenti, hogy a lekérdezés enumerálásáig nem kezdődnek meg a végrehajtásuk. Az elsődleges különbség az, hogy a PLINQ megpróbálja teljes mértékben kihasználni a rendszer összes processzorát. Ezt úgy hajtja végre, hogy az adatforrást szegmensekre particionálta, majd a lekérdezést az egyes szegmenseken külön feldolgozószálakon, párhuzamosan több processzoron hajtja végre. A párhuzamos végrehajtás sok esetben azt jelenti, hogy a lekérdezés jelentősen gyorsabban fut.

A párhuzamos végrehajtás révén a PLINQ jelentős teljesítménybeli javulást érhet el bizonyos lekérdezések régi kódjainál, gyakran csak úgy, hogy hozzáadja a AsParallel lekérdezési műveletet az adatforráshoz. A párhuzamosság azonban saját összetettségeket is bevezethet, és nem minden lekérdezési művelet fut gyorsabban a PLINQ-ban. Valójában a párhuzamosítás bizonyos lekérdezéseket lelassít. Ezért tisztában kell lennie azzal, hogy az olyan problémák, mint a rendelés, hogyan befolyásolják a párhuzamos lekérdezéseket. További információ: Understanding Speedup in PLINQ.

Feljegyzés

Ez a dokumentáció lambdakifejezésekkel definiálja a meghatalmazottakat a PLINQ-ban. Ha nem ismeri a lambda kifejezéseket a C# vagy a Visual Basic alkalmazásban, olvassa el a Lambda-kifejezéseket a PLINQ-ban és a TPL-ben.

A cikk további része áttekintést nyújt a fő PLINQ-osztályokról, és ismerteti a PLINQ-lekérdezések létrehozásának módját. Minden szakasz részletesebb információkra és kód példákra mutató hivatkozásokat tartalmaz.

A ParallelEnumerable osztály

Az System.Linq.ParallelEnumerable osztály a PLINQ szinte minden funkcióját elérhetővé teszi. Ez és a System.Linq többi névtértípus a System.Core.dll szerelvénybe lesz lefordítva. A Visual Studio alapértelmezett C# és Visual Basic projektjei egyaránt hivatkoznak a szerelvényre, és importálják a névteret.

ParallelEnumerable Magában foglalja az összes szabványos lekérdezési operátor implementációját, amelyet a LINQ to Objects támogat, bár nem próbálja meg párhuzamosítani az egyeseket. Ha nem ismeri a LINQ-t, olvassa el a LINQ (C#) és a LINQ (Visual Basic) bemutatása című témakört.

A szabványos lekérdezési operátorok mellett az ParallelEnumerable osztály olyan metódusokat is tartalmaz, amelyek lehetővé teszik a párhuzamos végrehajtásra vonatkozó viselkedéseket. Ezek a PLINQ-specifikus metódusok az alábbi táblázatban találhatók.

ParallelEnumerable operátor Leírás
AsParallel A PLINQ belépési pontja. Megadja, hogy a lekérdezés többi részét párhuzamosnak kell-e lennie, ha lehetséges.
AsSequential Azt határozza meg, hogy a lekérdezés többi részét egymás után, nem párhuzamos LINQ-lekérdezésként kell futtatni.
AsOrdered Megadja, hogy a PLINQ-nak meg kell őriznie a forrásütemezés sorrendjét a lekérdezés többi részében, vagy amíg a rendezés nem változik, például egy orderby (Order By in Visual Basic) záradék használatával.
AsUnordered Megadja, hogy a lekérdezés többi részéhez tartozó PLINQ nem szükséges a forrásütemezés sorrendjének megőrzéséhez.
WithCancellation Azt határozza meg, hogy a PLINQ rendszeres időközönként monitorozza a megadott lemondási jogkivonat állapotát, és szükség esetén megszakítsa a végrehajtást.
WithDegreeOfParallelism Megadja, hogy a PLINQ hány processzort használjon a lekérdezés párhuzamosításához.
WithMergeOptions Tippet ad arról, hogy a PLINQ-nak, ha lehetséges, hogyan kell egyesítenie a párhuzamos eredményeket egyetlen sorozatba a fogyasztó szálon.
WithExecutionMode Megadja, hogy a PLINQ párhuzamosítsa-e a lekérdezést akkor is, ha az alapértelmezett viselkedés az lenne, hogy egymás után futtassa a lekérdezést.
ForAll Többszálú enumerálási módszer, amely a lekérdezés eredményeinek iterálásával ellentétben lehetővé teszi az eredmények párhuzamos feldolgozását anélkül, hogy először egyesítenének a fogyasztói szálra.
Aggregate Túlterhelés A PLINQ-ra jellemző túlterhelés, amely lehetővé teszi a szál-helyi partíciók köztes összesítését, valamint egy végső összesítési függvényt az összes partíció eredményeinek egyesítéséhez.

A bejelentkezési modell

Lekérdezés írásakor a PLINQ-t úgy választhatja ki, hogy a ParallelEnumerable.AsParallel bővítménymetódust az adatforráson invoktálja az alábbi példában látható módon.

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

A AsParallel bővítménymetódus ebben az esetben where a későbbi lekérdezési operátorokat és selectaz implementációkat köti össze System.Linq.ParallelEnumerable .

Végrehajtási módok

Alapértelmezés szerint a PLINQ konzervatív. Futásidőben a PLINQ-infrastruktúra elemzi a lekérdezés általános szerkezetét. Ha a lekérdezés valószínűleg párhuzamosítással gyorsít, a PLINQ a forrásütemezést olyan feladatokra particionálja, amelyek egyidejűleg futtathatók. Ha nem biztonságos párhuzamosítani egy lekérdezést, a PLINQ csak egymás után futtatja a lekérdezést. Ha a PLINQ választhat egy potenciálisan költséges párhuzamos algoritmus vagy egy olcsó szekvenciális algoritmus között, alapértelmezés szerint a szekvenciális algoritmust választja. A metódus és az enumerálás használatával WithExecutionMode utasíthatja a System.Linq.ParallelExecutionMode PLINQ-t a párhuzamos algoritmus kiválasztására. Ez akkor hasznos, ha teszteléssel és méréssel tudja, hogy egy adott lekérdezés gyorsabban fut párhuzamosan. További információ : A végrehajtási mód megadása a PLINQ-ban.

A párhuzamosság foka

Alapértelmezés szerint a PLINQ a gazdaszámítógép összes processzorát használja. A módszer használatával WithDegreeOfParallelism utasíthatja a PLINQ-t, hogy legfeljebb egy megadott számú processzort használjon. Ez akkor hasznos, ha meg szeretné győződni arról, hogy a számítógépen futó egyéb folyamatok bizonyos mennyiségű processzoridőt kapnak. Az alábbi kódrészlet legfeljebb két processzor használatára korlátozza a lekérdezést.

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

Azokban az esetekben, amikor egy lekérdezés jelentős mennyiségű, nem számításhoz kötött munkát végez, például a fájl I/O fájlját, hasznos lehet a párhuzamosság fokának meghatározása, amely nagyobb, mint a gép magjainak száma.

Rendezett és rendezetlen párhuzamos lekérdezések

Egyes lekérdezésekben a lekérdezési operátornak olyan eredményeket kell létrehoznia, amelyek megőrzik a forrásütemezés sorrendjét. A PLINQ erre a célra biztosítja az AsOrdered operátort. AsOrdered különbözik a AsSequential. A AsOrdered sorozatok feldolgozása továbbra is párhuzamosan történik, de az eredmények pufferelve és rendezve vannak. Mivel a sorrend megőrzése általában többletmunkát igényel, előfordulhat, hogy egy AsOrdered sorozat lassabban dolgoz fel, mint az alapértelmezett AsUnordered sorrend. Számos tényezőtől függ, hogy egy adott rendezett párhuzamos művelet gyorsabb-e, mint a művelet szekvenciális verziója.

Az alábbi példakód bemutatja, hogyan választhatja ki a megőrzést.

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


További információ: Order Preservation in PLINQ.

Párhuzamos és szekvenciális lekérdezések

Egyes műveletekhez a forrásadatok szekvenciális módon történő kézbesítését kell megkövetelni. A ParallelEnumerable lekérdezési operátorok szükség esetén automatikusan visszaállnak a szekvenciális módra. A felhasználó által definiált lekérdezési operátorok és a felhasználói meghatalmazottak esetében, amelyek szekvenciális végrehajtást igényelnek, a PLINQ biztosítja a metódust AsSequential . Ha használja AsSequential, a lekérdezés összes további operátora egymás után lesz végrehajtva, amíg a rendszer újra nem AsParallel hívja. További információ : Párhuzamos és szekvenciális LINQ-lekérdezések kombinálása.

A lekérdezési eredmények egyesítésének beállításai

Ha egy PLINQ-lekérdezés párhuzamosan fut, az egyes munkavégző szálak eredményeit vissza kell vonni a fő szálra egy foreach ciklussal (For Each a Visual Basicben), vagy be kell szúrni egy listába vagy tömbbe. Bizonyos esetekben hasznos lehet egy adott típusú egyesítési művelet megadása, például az eredmények gyorsabb előállításának megkezdése. Ehhez a PLINQ támogatja a WithMergeOptions metódust és az enumerálást ParallelMergeOptions . További információ: Egyesítési beállítások a PLINQ-ban.

A ForAll operátor

A szekvenciális LINQ-lekérdezésekben a végrehajtás halasztva lesz, amíg a lekérdezés egy ( Visual Basic) ciklusban foreach vagy egy metódus meghívásával ( például ToList , ToArray vagy ToDictionary.For Each A PLINQ-ban a lekérdezés végrehajtására és az eredmények iterálására is használható foreach . foreach Önmagában azonban nem fut párhuzamosan, ezért megköveteli, hogy az összes párhuzamos tevékenység kimenetét vissza kell vonni abba a szálba, amelyen a hurok fut. A PLINQ-ban akkor foreach használható, ha meg kell őriznie a lekérdezési eredmények végleges sorrendjét, és akkor is, ha az eredményeket soros módon dolgozza fel, például amikor az egyes elemeket hívja Console.WriteLine meg. A lekérdezések gyorsabb végrehajtásához, ha a rendelések megőrzése nem szükséges, és ha az eredmények feldolgozása párhuzamosan is elvégezhető, használja a ForAll metódust a PLINQ-lekérdezés végrehajtásához. ForAll nem hajtja végre ezt az utolsó egyesítési lépést. Az alábbi példakód bemutatja a ForAll metódus használatát. System.Collections.Concurrent.ConcurrentBag<T> azért van itt használva, mert több szál egyidejű hozzáadására van optimalizálva anélkül, hogy megkísérelné eltávolítani az elemeket.

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

Az alábbi ábra a lekérdezések végrehajtásának különbségét foreachForAll mutatja be.

ForAll vs. ForEach

Érvénytelenítés

A PLINQ integrálva van a .NET lemondási típusaival. (További információ: Lemondás felügyelt szálakban.) Ezért az objektumokra vonatkozó szekvenciális LINQ-lekérdezésekkel ellentétben a PLINQ-lekérdezések megszakíthatók. Visszavonható PLINQ-lekérdezés létrehozásához használja a WithCancellation lekérdezés operátorát, és adjon meg egy példányt CancellationToken argumentumként. Ha a IsCancellationRequested jogkivonat tulajdonsága igaz értékre van állítva, a PLINQ megfigyeli, leállítja az összes szál feldolgozását, és eldob egy OperationCanceledException.

Lehetséges, hogy a PLINQ-lekérdezések a lemondási jogkivonat beállítása után is feldolgoznak bizonyos elemeket.

A nagyobb válaszképesség érdekében a hosszú ideig futó felhasználói meghatalmazottak lemondási kérelmeire is válaszolhat. További információ : A PLINQ-lekérdezés megszakítása.

Kivételek

A PLINQ-lekérdezések végrehajtásakor egyszerre több kivétel is előfordulhat a különböző szálakból. A kivételt kezelő kód más szálon is lehet, mint a kivételt okozó kód. A PLINQ a AggregateException típus használatával foglalja össze a lekérdezés által kidobott összes kivételt, és a hívó szálra visszaállítja ezeket a kivételeket. A hívószálon csak egy próbafogási blokkra van szükség. Azonban végigvezetheti az összes olyan kivételen, amely bele van foglalva a AggregateException fájlba, és elkaphat minden olyan kivételt, amelyből biztonságosan helyreállhat. Ritka esetekben előfordulhat, hogy néhány kivétel nem burkolt AggregateException, és ThreadAbortExceptionnem is burkolt.

Ha a kivételek visszaugranak az összekapcsolt szálra, lehetséges, hogy a lekérdezés a kivétel felmerülése után is feldolgoz néhány elemet.

További információ : A kivételek kezelése PLINQ-lekérdezésekben.

Egyéni particionálók

Bizonyos esetekben javíthatja a lekérdezési teljesítményt egy egyéni particionáló megírásával, amely kihasználja a forrásadatok néhány jellemzőjét. A lekérdezésben maga az egyéni particionáló a lekérdezett számbavételi objektum.

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

A PLINQ rögzített számú partíciót támogat (bár az adatok dinamikusan hozzárendelhetők ezekhez a partíciókhoz a terheléselosztás futási ideje alatt.) For és ForEach csak a dinamikus particionálást támogatja, ami azt jelenti, hogy a partíciók száma futásidőben változik. További információ: Egyéni particionálók a PLINQ-hoz és a TPL-hez.

A PLINQ teljesítményének mérése

A lekérdezések sok esetben párhuzamosak lehetnek, de a párhuzamos lekérdezés beállításának többlettere meghaladja a teljesítménybeli előnyöket. Ha egy lekérdezés nem végez sok számítást, vagy ha az adatforrás kicsi, a PLINQ-lekérdezések lassabbak lehetnek, mint az objektumokra vonatkozó linq szekvenciális lekérdezések. A Visual Studio Team Server párhuzamos Teljesítményelemző használatával összehasonlíthatja a különböző lekérdezések teljesítményét, megkeresheti a feldolgozási szűk keresztmetszeteket, és meghatározhatja, hogy a lekérdezés párhuzamosan vagy egymás után fut-e. További információ: Concurrency Visualizer és How to: Measure PLINQ Query Performance.

Lásd még