A PLINQ, talán kitalálható módon a paralell, azaz párhuzamos LINQ rövidítése. Úgy működik, mint a szekvenciális, annyi különbséggel, hogy a processzor összes szálát megpróbálja munkára fogni, illetve könnyen belátható okokból csak a LINQ to Objects esetében használható.
Normális esetben az LINQ kifejezésünkön az AsParallel()
meghívásával meg is kapjuk a kívánt működést, de ez józan ésszel belátható szabályrendszerben működik csak. A végrehajtás előtt a keretrendszer kielemzi a lekérdezést, és ha úgy látja, hogy semmi értelme párhuzamosítani, akkor nem is fogja megtenni.
Ebben az esetben hiába az AsParallel()
hívás, szépen szekvenciálisan fog futni a kódunk, de kézzel rá tudjuk erőltetni a párhuzamosítást. Erre való a WithExecutionMode(ParallelExecutionMode.ForceParallelism)
metódus, amivel felül tudjuk írni ezt a fajta működést, de nem biztos, hogy van értelme.
Nézzük meg, mi történik általános esetben:
using System;
using System.Collections.Generic;
using System.Linq;
namespace PeldaParallel3
{
class Program
{
static void Main(string[] args)
{
var collection = new List<int>();
for (int i = 0; i < 444; i += 2)
{
collection.Add(i);
}
var q = from c in collection.AsParallel()
where c % 5 == 0
select c;
q.ForAll(x => Console.Write("{0} ", x));
Console.ReadKey();
}
}
}
A kimenet:
340 350 360 370 380 390 400 410 420 430 440 230 240 250 260 270 280 290 300 310 320 330 0 120 130 140 150 160 170 180 190 200 210 220 10 20 30 40 50 60 70 80 90 100 110
A lekérdezés lefut, megkapjuk az eredményeket. Itt nagyon jól látható a particionálás, a számok szépen követik egymást, de adagonként. És az is teljesen jól játszik, hogy tényleg csak az AsParallel
kell és működik. Na de mi a helyzet a sorrenddel? Ez részben a ForAll
miatt van, ugyanis az nem igényli, hogy a teljes kollekció fel legyen töltve, ellenben minden sorrendiséget mellőz. Először is, írjuk át a kiíratást foreach
segítségével, majd az AsParallel
után hívjuk meg az AsOrdered
metódust is.
using System;
using System.Collections.Generic;
using System.Linq;
namespace PeldaParallel4
{
class Program
{
static void Main(string[] args)
{
var collection = new List<int>();
for (int i = 0; i < 444; i += 2)
{
collection.Add(i);
}
var q = from c in collection.AsParallel().AsOrdered()
where c % 5 == 0
select c;
foreach (var item in q)
{
Console.Write("{0} ", item);
}
Console.ReadKey();
}
}
}
Ebben az esetben a kiírás sorrendben fog történni.
0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230
240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 430 440
Érdemes megjegyezni, hogy az AsOrdered()
hívás miatt lassabban fog futni a kód, mert a keretrendszernek emellett még figyelnie kell az eredeti sorrend megtartására is.
Szekvenciális futást befolyásoló tényezők
Mint említettük korábban, a keretrendszer dönthet úgy, hogy nem éri meg párhuzamosan futtatni a megadott LINQ kérést. Számos tényező tud erre hatással lenni. A legfontosabb tényezők:
- A kollekció, amin futtatjuk a LINQ kérést, nagyon kevés elemet tartalmaz és tovább tart párhuzamosítani, mint ténylegesen lefuttatni
- Az olyan kérések sorrendben fognak futni, amelyekben van
Take
,TakeWhile
,Skip
vagySkipWhile
- Az olyan kérések, amelyekben van
Concat
,Reverse
,Zip
vagySequenceEquals
, sorrendben fognak futni, ha a kollekció nem tömb vagy lista. - Az olyan kérések sorrendben fognak futni, amelyekben valamilyen szűrés megváltoztatta vagy eltávolította az elemek eredeti sorredjét és az elem indexére vonatkozó
Select
,SelectMany
vagyElementAt
hívás van.