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

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

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

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

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)

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

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

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)

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

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

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

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

Следующие операторы запроса налагают сохранение порядка на все последующие операции запроса, пока не встретится вызов AsUnordered.

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

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

Оператор Результат для упорядоченной исходной последовательности Результат для неупорядоченной исходной последовательности
Aggregate Недетерминированные выходные данные для неассоциативных или некоммутативных операций Недетерминированные выходные данные для неассоциативных или некоммутативных операций
All Неприменимо Нет данных
Any Нет данных Нет данных
AsEnumerable Нет данных Неприменимо
Average Недетерминированные выходные данные для неассоциативных или некоммутативных операций Недетерминированные выходные данные для неассоциативных или некоммутативных операций
Cast Упорядоченные результаты Неупорядоченные результаты
Concat Упорядоченные результаты Неупорядоченные результаты
Count Неприменимо Нет данных
DefaultIfEmpty Нет данных Неприменимо
Distinct Упорядоченные результаты Неупорядоченные результаты
ElementAt Возвращает указанный элемент Произвольный элемент
ElementAtOrDefault Возвращает указанный элемент Произвольный элемент
Except Неупорядоченные результаты Неупорядоченные результаты
First Возвращает указанный элемент Произвольный элемент
FirstOrDefault Возвращает указанный элемент Произвольный элемент
ForAll Недетерминированные результаты параллельного выполнения Недетерминированные результаты параллельного выполнения
GroupBy Упорядоченные результаты Неупорядоченные результаты
GroupJoin Упорядоченные результаты Неупорядоченные результаты
Intersect Упорядоченные результаты Неупорядоченные результаты
Join Упорядоченные результаты Неупорядоченные результаты
Last Возвращает указанный элемент Произвольный элемент
LastOrDefault Возвращает указанный элемент Произвольный элемент
LongCount Неприменимо Нет данных
Min Нет данных Неприменимо
OrderBy Переупорядочивает последовательность Начинает новый раздел с контролем порядка
OrderByDescending Переупорядочивает последовательность Начинает новый раздел с контролем порядка
Range Неприменимо (значение по умолчанию такое же, как для AsParallel) Нет данных
Repeat Неприменимо (значение по умолчанию такое же, как для AsParallel) Нет данных
Reverse Изменяет порядок на обратный Ничего не делает
Select Упорядоченные результаты Неупорядоченные результаты
Select (индексированный) Упорядоченные результаты Неупорядоченные результаты
SelectMany Упорядоченные результаты Неупорядоченные результаты
SelectMany (индексированный) Упорядоченные результаты Неупорядоченные результаты
SequenceEqual Упорядоченное сравнение Неупорядоченное сравнение
Single Неприменимо Нет данных
SingleOrDefault Нет данных Неприменимо
Skip Пропускает первые n элементов Пропускает любые n элементов
SkipWhile Упорядоченные результаты Недетерминированный результат. Выполняет метод SkipWhile для текущего произвольного порядка
Sum Недетерминированные выходные данные для неассоциативных или некоммутативных операций Недетерминированные выходные данные для неассоциативных или некоммутативных операций
Take Отбирает первые n элементов Отбирает любые n элементов
TakeWhile Упорядоченные результаты Недетерминированный результат. Выполняет метод TakeWhile для текущего произвольного порядка
ThenBy Дополняет OrderBy Дополняет OrderBy
ThenByDescending Дополняет OrderBy Дополняет OrderBy
ToArray Упорядоченные результаты Неупорядоченные результаты
ToDictionary Неприменимо Неприменимо
ToList Упорядоченные результаты Неупорядоченные результаты
ToLookup Упорядоченные результаты Неупорядоченные результаты
Union Упорядоченные результаты Неупорядоченные результаты
Where Упорядоченные результаты Неупорядоченные результаты
Where (индексированный) Упорядоченные результаты Неупорядоченные результаты
Zip Упорядоченные результаты Неупорядоченные результаты

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

См. также