776
Глава.27 .Асинхронные.вычислительные.операции
боре данных последовательно обрабатываются одним потоком — это называется
последовательным запросом
(sequential query). Повысить
производительность
можно при помощи языка параллельных запросов (Parallel LINQ), позволяющего
последовательный запрос превратить в
параллельный
(parallel query). Последний
во внутренней реализации задействует задания (поставленные в очередь пла-
нировщиком, используемым по умолчанию), распределяя элементы коллекции
среди нескольких процессоров для обработки. Как и в случае с методами класса
Parallel
, максимальный выигрыш достигается при наличии множество элемен-
тов для обработки или когда обработка каждого
элемента представляет собой
длительную вычислительную операцию.
Вся функциональность Parallel LINQ реализована в статическом классе
System.
Linq.ParallelEnumerable
(он определен в библиотеке
System.Core.dll
), поэтому
в код следует импортировать пространство имен
System.Linq
. В частности, этот
класс содержит параллельные версии всех стандартных LINQ-операторов, таких
как
Where
,
Select
,
SelectMany
,
GroupBy
,
Join
,
OrderBy
,
Skip
,
Take
и т. п. Все эти
методы являются
методами расширения типа
System.Linq.ParallelQuery
. Для
вызова их параллельных версий следует преобразовать последовательный запрос
(основанный на интерфейсе
IEnumerable
или
IEnumerable
) в параллельный
(основанный на классе
ParallelQuery
или
Parallel Query
), воспользовавшись
методом расширения
AsParallel
класса
ParallelEnumerable
, который выглядит
следующим образом
1
:
public static ParallelQuery AsParallel(
this IEnumerable source)
public static ParallelQuery AsParallel(this IEnumerablesource)
Вот пример преобразования последовательного запроса в параллельный. Он
возвращает все устаревшие методы, определенные в сборке:
private static void ObsoleteMethods(Assembly assembly) {
var query =
from type in assembly.GetExportedTypes().AsParallel()
from method in type.GetMethods(BindingFlags.Public |
BindingFlags.Instance | BindingFlags.Static)
let obsoleteAttrType = typeof(ObsoleteAttribute)
where Attribute.IsDefined(method, obsoleteAttrType)
orderbytype.FullName
let obsoleteAttrObj = (ObsoleteAttribute)
Attribute.GetCustomAttribute(method, obsoleteAttrType)
select String.Format("Type={0}\nMethod={1}\nMessage={2}\n",
1
Класс ParallelQuery является производным от ParallelQuery.
777
Встроенный.язык.параллельных.запросов
type.FullName, method.ToString(), obsoleteAttrObj.Message);
// Вывод результатов
foreach (var result in query) Console.WriteLine(result);
}
Хотя
подобные решения не типичны, также существует возможность в ходе опе-
раций переключиться с параллельного режима на последовательный. Это делается
при помощи метода
AsSequential
класса
ParallelEnumerable
:
public static IEnumerable
AsSequential(
this ParallelQuery source)
Этот метод преобразует
ParallelQuery
в интерфейс
IEnumerable
, и все
операции начинают выполняться всего одним потоком.
Обычно результат LINQ-запроса вычисляется потоком, исполняющим ин-
струкцию
foreach
(как было показано ранее). Это означает, что все результаты
запроса просматриваются всего одним потоком. Параллельный режим обработки
обеспечивает метод
ForAll
класса
ParallelEnumerable
:
static void ForAll(
this ParallelQuery source, Action action)
Этот метод позволяет нескольким потокам одновременно обрабатывать ре-
зультаты запросов. Мой приведенный ранее код с помощью этого метода можно
переписать следующим образом:
// вывод результатов
query.ForAll(Console.WriteLine);
Однако одновременный вызов метода
Console.WriteLine
несколькими потоками
отрицательно сказывается на производительности, так как класс
Console
внутренне
синхронизирует потоки, гарантируя, что к консоли в каждый момент времени имеет
доступ только один поток. Именно это предотвращает смешение вывода потоков,
из-за которого может появиться непонятный результат. Используйте метод
ForAll
в случаях, когда требуется вычисление каждого из результатов.
Так как при параллельном LINQ-запросе элементы обрабатываются несколь-
кими потоками одновременно, результаты возвращаются в произвольном порядке.
Для сохранения очередности обработки элементов применяется метод
AsOrdered
класса
ParallelEnumerable
. С его помощью потоки разбивают элементы по груп-
пам, которые впоследствии сливаются друг с другом. Однако все это отрицательно
сказывается на производительности. Вот операторы, предназначенные для вы-
полнения неупорядоченных операций:
Distinct
,
Except
,
Intersect
,
Union
,
Join
,
GroupBy
,
GroupJoin
и
ToLookup
. После любого из этих операторов можно вызвать
метод
AsOrdered
, чтобы упорядочить элементы.
Следующие операторы выполняют упорядоченные операции:
OrderBy
,
OrderByDescending
,
ThenBy
и
ThenByDescending
. Если вы хотите вернуться к не-
упорядоченным операциям, чтобы повысить производительность, после любого из
этих операторов также можно вызвать метод
AsUnordered
.