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.

Zobacz też