Поделиться через


Сохранение порядка в PLINQ

Цель PLINQ — максимально повысить производительность при сохранении правильности. Запрос должен выполняться как можно быстрее, но по-прежнему выдавать правильные результаты. В некоторых случаях для поддержания правильности требуется сохранить порядок исходной последовательности. Однако для этого могут потребоваться большие затраты компьютерных ресурсов. Таким образом, по умолчанию в PLINQ не сохраняется порядок исходной последовательности. В этом отношении PLINQ напоминает LINQ to SQL, но не похож на запрос LINQ to Objects, в котором порядок сохраняется.

Чтобы переопределить поведение по умолчанию, можно включить сохранение порядка в исходной последовательности с помощью оператора AsOrdered. Сохранение порядка можно отключить позже в запросе с помощью метода AsUnordered<TSource>. Запрос обрабатывается обоими методами на основании эвристики, определяющей необходимость параллельного или последовательного выполнения запроса. Дополнительные сведения см. в разделе Общее представление об ускорении выполнения в PLINQ.

В следующем примере показан неупорядоченный параллельный запрос, который выполняет фильтрацию для всех элементов, соответствующих условию, не пытаясь упорядочить результаты каким-либо образом.

Dim cityQuery = From city In cities.AsParallel()
               Where City.Population > 10000
               Take (1000)
var cityQuery = (from city in cities.AsParallel()
                 where city.Population > 10000
                 select city)
                   .Take(1000);

В ответ на этот запрос не обязательно выдаются первые 1000 городов в исходной последовательности, которые удовлетворяют условию, вместо этого выдается некоторый набор из 1000 городов, удовлетворяющих условию. Операторы запроса PLINQ разделяют исходную последовательность на несколько подпоследовательностей, которые обрабатываются как одновременные задачи. Если сохранение порядка не задано, результаты из каждой части передаются на следующий этап запроса в произвольном порядке. Кроме того, разделение может привести к созданию подмножества результатов до того, как начнется обработка оставшихся элементов. Полученный порядок может каждый раз отличаться. Приложение не может управлять эти процессом, поскольку оно зависит от того, как операционная система планирует потоки.

В следующем примере переопределяется поведение по умолчанию в исходной последовательности с помощью оператора AsOrdered. Это гарантирует возврат методом Take<TSource> первых 10 городов в исходной последовательности, отвечающих условию.

Dim orderedCities = From city In cities.AsParallel().AsOrdered()
                    Where City.Population > 10000
                    Take (1000)
            var orderedCities = (from city in cities.AsParallel().AsOrdered()
                                 where city.Population > 10000
                                 select city)
                                .Take(1000);

Однако этот запрос, возможно, не выполняется так же быстро, как неупорядоченная версия, поскольку в нем должен отслеживаться исходный порядок во всех частях исходной последовательности и во время слияния обеспечиваться соответствующий порядок. Следовательно, метод AsOrdered рекомендуется использовать только при необходимости и только для тех частей запроса, которые его требуют. Если сохранение порядка больше не требуется, используйте метод AsUnordered<TSource> для его отключения. В следующем примере это достигается путем создания двух запросов.

        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

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 { Name = city.Name, Pop = city.Population, Mayor = c.Mayor };

foreach (var city in finalResult) { /*...*/ }

Обратите внимание, что в PLINQ сохраняется порядок последовательности, созданный с помощью операторов порядка для остальной части запроса. Другими словами, операторы, такие как OrderBy и ThenBy, обрабатываются, как если бы за ними следовал вызов метода AsOrdered.

Операторы запросов и упорядочение

Следующие операторы запросов обеспечивают сохранение порядка во всех последующих операциях в запросе или до тех пор, пока не вызывается метод AsUnordered<TSource>.

Следующие операторы запросов PLINQ могут в некоторых случаях требовать выдачи правильных результатов упорядоченных исходных последовательностей.

Поведение некоторых операторов запросов PLINQ отличается в зависимости от того, является их исходная последовательность упорядоченной или неупорядоченной. Эти операторы перечислены в следующей таблице.

Оператор

Результат при упорядоченной исходной последовательности

Результат при неупорядоченной исходной последовательности

Aggregate

Недетерминированные выходные данные для неассоциативных или некоммутативных операций

Недетерминированные выходные данные для неассоциативных или некоммутативных операций

All<TSource>

Неприменимо

Неприменимо

Any

Неприменимо

Неприменимо

AsEnumerable<TSource>

Неприменимо

Неприменимо

Average

Недетерминированные выходные данные для неассоциативных или некоммутативных операций

Недетерминированные выходные данные для неассоциативных или некоммутативных операций

Cast<TResult>

Упорядоченные результаты

Неупорядоченные результаты

Concat

Упорядоченные результаты

Неупорядоченные результаты

Count

Неприменимо

Неприменимо

DefaultIfEmpty

Неприменимо

Неприменимо

Distinct

Упорядоченные результаты

Неупорядоченные результаты

ElementAt<TSource>

Возвращает указанный элемент

Произвольный элемент

ElementAtOrDefault<TSource>

Возвращает указанный элемент

Произвольный элемент

Except

Неупорядоченные результаты

Неупорядоченные результаты

First

Возвращает указанный элемент

Произвольный элемент

FirstOrDefault

Возвращает указанный элемент

Произвольный элемент

ForAll<TSource>

Выполняется недетерминированно параллельно

Выполняется недетерминированно параллельно

GroupBy

Упорядоченные результаты

Неупорядоченные результаты

GroupJoin

Упорядоченные результаты

Неупорядоченные результаты

Intersect

Упорядоченные результаты

Неупорядоченные результаты

Join

Упорядоченные результаты

Неупорядоченные результаты

Last

Возвращает указанный элемент

Произвольный элемент

LastOrDefault

Возвращает указанный элемент

Произвольный элемент

LongCount

Неприменимо

Неприменимо

Min

Неприменимо

Неприменимо

OrderBy

Переупорядочивает последовательность

Запускает новую упорядоченную часть

OrderByDescending

Переупорядочивает последовательность

Запускает новую упорядоченную часть

Range

Неприменимо (такое же значение по умолчанию, что и AsParallel)

Неприменимо

Repeat<TResult>

Неприменимо (такое же значение по умолчанию, как AsParallel)

Неприменимо

Reverse<TSource>

Изменяет порядок на обратный

Не выполняет действий

Select

Упорядоченные результаты

Неупорядоченные результаты

Select (индексирован)

Упорядоченные результаты

Неупорядоченные результаты

SelectMany

Упорядоченные результаты

Неупорядоченные результаты

SelectMany (индексирован)

Упорядоченные результаты

Неупорядоченные результаты

SequenceEqual

Упорядоченное сравнение

Неупорядоченное сравнение

Single

Неприменимо

Неприменимо

SingleOrDefault

Неприменимо

Неприменимо

Skip<TSource>

Пропускает первые элементы в количестве n

Пропускает любые элементы в количестве n

SkipWhile

Упорядоченные результаты

Недетерминированный. Выполняет метод SkipWhile для текущего произвольного порядка

Sum

Недетерминированные выходные данные для неассоциативных или некоммутативных операций

Недетерминированные выходные данные для неассоциативных или некоммутативных операций

Take<TSource>

Принимает первые элементы в количестве n

Принимает любые элементы в количестве n

TakeWhile

Упорядоченные результаты

Недетерминированный. Выполняет метод TakeWhile для текущего произвольного порядка

ThenBy

Дополняет метод OrderBy

Дополняет метод OrderBy

ThenByDescending

Дополняет метод OrderBy

Дополняет метод OrderBy

ToTSource[]

Упорядоченные результаты

Неупорядоченные результаты

ToDictionary

Неприменимо

Неприменимо

ToList<TSource>

Упорядоченные результаты

Неупорядоченные результаты

ToLookup

Упорядоченные результаты

Неупорядоченные результаты

Union

Упорядоченные результаты

Неупорядоченные результаты

Where

Упорядоченные результаты

Неупорядоченные результаты

Where (индексирован)

Упорядоченные результаты

Неупорядоченные результаты

Zip

Упорядоченные результаты

Неупорядоченные результаты

Неупорядоченные результаты активно не перемешиваются, к ним просто не применяется никакая особая логика сортировки. В некоторых случаях неупорядоченный запрос может сохранять порядок исходной последовательности. Для запросов, использующих индексированный оператор Select, PLINQ всегда выводит выходные элементы в порядке возрастания индексов, однако не гарантирует, какие индексы для каких элементов будут назначены.

См. также

Основные понятия

Parallel LINQ (PLINQ)

Параллельное программирование в .NET Framework