Сохранение порядка в 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 гарантирует порядок возрастания индексов для выходных элементов, но не гарантирует конкретные индексы для этих элементов.