Параллельная обработка данных
Задача: Имеется коллекция данных. Требуется выполнить одну и ту же операцию с каждым элементом. Эта операция является ограниченной по вычислениям и может занять некоторое время.
Решение
Тип
Parallel содержит метод ForEach, разработанный специально для этой задачи. Следующий пример получает коллекцию матриц и поворачивает эти матрицы:void Rotate(IEnumerable<Matrix> matrices, float degrees)Возможны ситуации, в которых преждевременно требуется прервать цикл (например, при обнаружении недействительного значения). Следующий пример обращает каждую матрицу, но при обнаружении недействительной матрицы цикл прерывается:
{
Parallel.ForEach(matrices, m => m.Rotate(degrees));
}
void Invert(IEnumerable<Matrix> matrices)Этот код использует
{
Parallel.ForEach(matrices, (m, state) =>
{
if (!m.IsInvertible)
state.Stop();
else
m.Invert();
});
}
ParallelLoopState.Stop для остановки цикла и предотвращения любых дальнейших вызовов тела цикла. Учтите, что цикл является параллельным, поэтому могут уже выполниться другие вызовы тела цикла, включая вызовы для элементов, следующих после текущего. В приведённом примере кода если третья матрица не является обратимой, то цикл прерывается и новые матрицы обрабатываться не будут, но может оказаться, что уже обрабатываются другие матрицы (например, четвертая и пятая).Более распространённая ситуация встречается тогда, когда требуется отменить параллельный цикл. Это не то же, что остановка: цикл останавливается изнутри и отменяется за своими пределами. Например, кнопка отмены может отменить
CancellationTokenSource, отменяя параллельный цикл, как в следующем примере:void Rotate (IEnumerable<Matrix> matrices,Следует иметь в виду, что каждая параллельная задача может выполняться в отдельном потоке, поэтому любое совместное состояние должно быть защищено. Следующий пример обращает каждую матрицу и подсчитывает количество матриц, которые обратить не удалось:
float degrees, CancellationToken t)
{
Parallel.ForEach(matrices,
new ParallelOptions { CancellationToken = t },
m => m.Rotate(degrees));
}
// Примечание: это не самая эффективная реализация.Метод
// Это лишь пример использования блокировки
// для защиты совместного состояния.
int Invert(IEnumerable<Matrix> matrices)
{
object mutex = new object();
int nonInvertible = 0;
Parallel.ForEach(matrices, m => {
if (matrix.IsInvertible)
m.Invert();
else
{
lock (mutex)
{
++nonInvertible;
}
}
});
return nonInvertible;
}
Parallel.ForEach предоставляет возможность параллельной обработки последовательности значений. Аналогичное решение Parallel LINQ (PLINQ) предоставляет практически те же возможности в LINQ-подобном синтаксисе. Одно из различий между Parallel и PLINQ заключается в том, что PLINQ предполагает, что может использовать все ядра на компьютере, тогда как Parallel может динамически реагировать на изменения условий процессора.Parallel.ForEach реализует параллельный цикл foreach. Если вам потребуется выполнить параллельный цикл for, то класс Parallel также поддерживает метод Parallel.For. Метод Parallel.For особенно полезен при работе с несколькими массивами данных, которые получают один индекс.Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 4.