Zamawianie zachowywania w PLINQ
W PLINQ celem jest zmaksymalizowanie wydajności przy zachowaniu poprawności. Zapytanie powinno działać tak szybko, jak to możliwe, ale nadal generuje poprawne wyniki. W niektórych przypadkach poprawność wymaga zachowania kolejności sekwencji źródłowej; jednak kolejność może być kosztowna obliczeniowo. W związku z tym domyślnie PLINQ nie zachowuje kolejności sekwencji źródłowej. W tym względzie PLINQ przypomina LINQ to SQL, ale jest w przeciwieństwie do LINQ to Objects, co zachowuje kolejność.
Aby zastąpić domyślne zachowanie, można włączyć zachowywanie kolejności przy użyciu AsOrdered operatora w sekwencji źródłowej. Następnie można wyłączyć zachowywanie kolejności w dalszej części zapytania przy użyciu AsUnordered metody . W obu metodach zapytanie jest przetwarzane na podstawie algorytmów heurystycznych, które określają, czy wykonać zapytanie jako równoległe, czy sekwencyjne. Aby uzyskać więcej informacji, zobacz Understanding Speedup in PLINQ (Opis szybkości w PLINQ).
W poniższym przykładzie pokazano nieurządkowaną kwerendę równoległą, która filtruje wszystkie elementy zgodne z warunkiem bez próby uporządkowania wyników w jakikolwiek sposób.
var cityQuery =
(from city in cities.AsParallel()
where city.Population > 10000
select city).Take(1000);
Dim cityQuery = From city In cities.AsParallel()
Where city.Population > 10000
Take (1000)
To zapytanie niekoniecznie generuje pierwsze 1000 miast w sekwencji źródłowej, które spełniają warunek, ale raczej niektóre z 1000 miast spełniających ten warunek. Operatory zapytań PLINQ dzielą sekwencję źródłową na wiele podsekwencjonowania, które są przetwarzane jako współbieżne zadania. Jeśli nie określono zachowania kolejności, wyniki z każdej partycji są przekazywane do następnego etapu zapytania w dowolnej kolejności. Ponadto partycja może uzyskać podzbiór wyników, zanim będzie nadal przetwarzać pozostałe elementy. Wynikowa kolejność może być różna za każdym razem. Aplikacja nie może tego kontrolować, ponieważ zależy od tego, jak system operacyjny planuje wątki.
Poniższy przykład zastępuje domyślne zachowanie przy użyciu AsOrdered operatora w sekwencji źródłowej. Dzięki Take temu metoda zwraca pierwsze 1000 miast w sekwencji źródłowej spełniającej warunek.
var orderedCities =
(from city in cities.AsParallel().AsOrdered()
where city.Population > 10000
select city).Take(1000);
Dim orderedCities = From city In cities.AsParallel().AsOrdered()
Where city.Population > 10000
Take (1000)
Jednak to zapytanie prawdopodobnie nie działa tak szybko, jak nieurządkowana wersja, ponieważ musi śledzić oryginalną kolejność w obrębie partycji i w czasie scalania, upewnij się, że kolejność jest spójna. W związku z tym zalecamy użycie AsOrdered tylko wtedy, gdy jest to wymagane, i tylko dla tych części zapytania, które tego wymagają. Gdy zachowywanie kolejności nie jest już wymagane, użyj polecenia AsUnordered , aby go wyłączyć. W poniższym przykładzie można to osiągnąć, tworząc dwa zapytania.
var orderedCities2 =
(from city in cities.AsParallel().AsOrdered()
where city.Population > 10000
select city).Take(1000);
var finalResult =
from city in orderedCities2.AsUnordered()
join p in people.AsParallel()
on city.Name equals p.CityName into details
from c in details
select new
{
city.Name,
Pop = city.Population,
c.Mayor
};
foreach (var city in finalResult) { /*...*/ }
Dim orderedCities2 = From city In cities.AsParallel().AsOrdered()
Where city.Population > 10000
Select city
Take (1000)
Dim finalResult = From city In orderedCities2.AsUnordered()
Join p In people.AsParallel() On city.Name Equals p.CityName
Select New With {.Name = city.Name, .Pop = city.Population, .Mayor = city.Mayor}
For Each city In finalResult
Console.WriteLine(city.Name & ":" & city.Pop & ":" & city.Mayor)
Next
Należy pamiętać, że PLINQ zachowuje kolejność sekwencji generowanej przez operatory nakładające kolejność dla pozostałej części zapytania. Innymi słowy, operatory, takie jak OrderBy i ThenBy , są traktowane tak, jakby były obserwowane przez wywołanie metody AsOrdered.
Operatory zapytań i kolejność
Następujące operatory zapytań wprowadzają zachowywanie kolejności do wszystkich kolejnych operacji w zapytaniu lub do momentu AsUnordered wywołania:
W niektórych przypadkach następujące operatory zapytań PLINQ mogą wymagać uporządkowanych sekwencji źródłowych w celu uzyskania poprawnych wyników:
Niektóre operatory zapytań PLINQ zachowują się inaczej, w zależności od tego, czy ich sekwencja źródłowa jest uporządkowana, czy nieurządkowana. W poniższej tabeli wymieniono te operatory.
Operator | Wynik, gdy sekwencja źródłowa jest uporządkowana | Wynik, gdy sekwencja źródłowa jest nieurządkowana |
---|---|---|
Aggregate | Nieokreślone dane wyjściowe dla operacji niekojarnych lub niekommutacyjnych | Nieokreślone dane wyjściowe dla operacji niekojarnych lub niekommutacyjnych |
All | Nie dotyczy | Nie dotyczy |
Any | Nie dotyczy | Nie dotyczy |
AsEnumerable | Nie dotyczy | Nie dotyczy |
Average | Nieokreślone dane wyjściowe dla operacji niekojarnych lub niekommutacyjnych | Nieokreślone dane wyjściowe dla operacji niekojarnych lub niekommutacyjnych |
Cast | Uporządkowane wyniki | Wyniki nieurządzane |
Concat | Uporządkowane wyniki | Wyniki nieurządzane |
Count | Nie dotyczy | Nie dotyczy |
DefaultIfEmpty | Nie dotyczy | Nie dotyczy |
Distinct | Uporządkowane wyniki | Wyniki nieurządzane |
ElementAt | Zwraca określony element | Dowolny element |
ElementAtOrDefault | Zwraca określony element | Dowolny element |
Except | Wyniki nieurządzane | Wyniki nieurządzane |
First | Zwraca określony element | Dowolny element |
FirstOrDefault | Zwraca określony element | Dowolny element |
ForAll | Wykonuje nieokreślono równolegle | Wykonuje nieokreślono równolegle |
GroupBy | Uporządkowane wyniki | Wyniki nieurządzane |
GroupJoin | Uporządkowane wyniki | Wyniki nieurządzane |
Intersect | Uporządkowane wyniki | Wyniki nieurządzane |
Join | Uporządkowane wyniki | Wyniki nieurządzane |
Last | Zwraca określony element | Dowolny element |
LastOrDefault | Zwraca określony element | Dowolny element |
LongCount | Nie dotyczy | Nie dotyczy |
Min | Nie dotyczy | Nie dotyczy |
OrderBy | Zmienia kolejność sekwencji | Rozpoczyna nową uporządkowaną sekcję |
OrderByDescending | Zmienia kolejność sekwencji | Rozpoczyna nową uporządkowaną sekcję |
Range | Nie dotyczy (ta sama wartość domyślna co AsParallel ) | Nie dotyczy |
Repeat | Nie dotyczy (ta sama wartość domyślna co AsParallel) | Nie dotyczy |
Reverse | Odwraca | Nic nie robi. |
Select | Uporządkowane wyniki | Wyniki nieurządzane |
Select (indeksowane) | Uporządkowane wyniki | Nieurządkowane wyniki. |
SelectMany | Uporządkowane wyniki. | Wyniki nieurządzane |
SelectMany (indeksowane) | Uporządkowane wyniki. | Nieurządkowane wyniki. |
SequenceEqual | Uporządkowane porównanie | Porównanie nieurządzane |
Single | Nie dotyczy | Nie dotyczy |
SingleOrDefault | Nie dotyczy | Nie dotyczy |
Skip | Pomija pierwsze n elementów | Pomija wszystkie n elementów |
SkipWhile | Uporządkowane wyniki. | Rodzaju. Wykonuje funkcję SkipWhile w bieżącej kolejności dowolnego |
Sum | Nieokreślone dane wyjściowe dla operacji niekojarnych lub niekommutacyjnych | Nieokreślone dane wyjściowe dla operacji niekojarnych lub niekommutacyjnych |
Take | Przyjmuje pierwsze n elementy |
Przyjmuje dowolne n elementy |
TakeWhile | Uporządkowane wyniki | Rodzaju. Wykonuje polecenie TakeWhile w bieżącej kolejności dowolnego |
ThenBy | Suplementy OrderBy |
Suplementy OrderBy |
ThenByDescending | Suplementy OrderBy |
Suplementy OrderBy |
ToArray | Uporządkowane wyniki | Wyniki nieurządzane |
ToDictionary | Nie dotyczy | Nie dotyczy |
ToList | Uporządkowane wyniki | Wyniki nieurządzane |
ToLookup | Uporządkowane wyniki | Wyniki nieurządzane |
Union | Uporządkowane wyniki | Wyniki nieurządzane |
Where | Uporządkowane wyniki | Wyniki nieurządzane |
Where (indeksowane) | Uporządkowane wyniki | Wyniki nieurządzane |
Zip | Uporządkowane wyniki | Wyniki nieurządzane |
Nieurządkowane wyniki nie są aktywnie potasowane; po prostu nie mają do nich żadnej specjalnej logiki porządkowania. W niektórych przypadkach zapytanie nieurządzane może zachować kolejność sekwencji źródłowej. W przypadku zapytań, które korzystają z indeksowanego operatora Select, PLINQ gwarantuje, że elementy wyjściowe pojawią się w kolejności rosnących indeksów, ale nie gwarantuje, które indeksy zostaną przypisane do których elementów.