Не все запросы могут стать автоматически быстрее при параллельном выполнении. Например, если запрос содержит только один пользовательский делегат с небольшим числом задач, то такой запрос последовательно выполняется быстрее. Это связано с различными дополнительными накладными расходами на управление параллельным выполнением. Однако, PLINQ позволяет, при необходимости, указать тот режим выполнения запроса, который вам необходим. Для этого используется один из методов расширения PLINQ — WithExecutionMode
.
Метод WithExecutionMode
В некоторых случаях у нас может быть больше сведений о сложности и ресурсоемкости запроса, чем доступно PLINQ. Когда иы точно уверены в том, что делегат в запросе затратный и в любом случае выиграет от применения параллелизации, то в этом случае мы можем указать PLINQ, что такой запрос должен выполняться только в параллельном режиме. Например,
IEnumerable<int> range = Enumerable.Range(0, 10000000); var square = range.AsParallel() .WithExecutionMode(ParallelExecutionMode.ForceParallelism) //указываем, что запрос должен выполняться в параллельном режиме .Select(s => Math.Sqrt(s));
здесь мы вызываем метод WithExecutionMode
с флагом ParallelExecutionMode.ForceParallelism
, заставляя, тем самым, PLINQ обработать запрос в параллельном режиме. При этом, флаг ParallelExecutionMode.ForceParallelism
может принимать следующие значения:
Default |
0 | Параметр по умолчанию. PLINQ изучит структуру запроса и выполнит его в параллельном режиме, только если это может привести к ускорению работы, иначе — PLINQ выполнит данный запрос как обычный запрос LINQ to Objects. |
ForceParallelism |
1 | Выполнять весь запрос параллельно, даже если для этого понадобится использовать алгоритмы, требующие много ресурсов. Этот флаг используется в тех случаях, когда известно, что параллельное выполнение запроса приведет к ускорению, но, при этом, в режиме по умолчанию PLINQ этот запрос выполняет как последовательный. |
В каких случаях PLINQ выполняет запрос последовательно?
Есть ряд условий при которых PLINQ по умолчанию будет выполнять запрос последовательно, а именно:
- запросы, содержащие предложение
Select
, а также индексированные инструкцииWhere
,SelectMany
илиElementAt
после оператора упорядочивания или фильтрации, которые удаляют или изменяют исходные индексы. - запросы, содержащие оператор
Take
,TakeWhile
,Skip
илиSkipWhile
, в которых индексы исходной последовательности не сохраняют исходный порядок. - запросы, которые содержат
Zip
илиSequenceEquals
, за исключением случаев, когда один из источников данных содержит изначально упорядоченный индекс, а другой источник данных можно проиндексировать (например, массив или IList(T)). - запросы, которые содержат оператор
Concat
, если он не применяется к индексируемым источникам данных. - запросы, содержащие оператор
Reverse
, если он не применяется к индексируемым источникам данных.
Итого
По умолчанию PLINQ анализирует запрос и, в зависимости от результата анализа, самостоятельно выбирает режим работы с запросом — параллельно или последовательно. Однако, если мы уверены, что в параллельном режиме мы точно повысим производительность нашего приложения, то мы можем принудительно заставить PLINQ выполнять запрос параллельно. Для этого мы используем метод WithExecutionMode
.