Параллельное агрегирование
Задача: требуется агрегировать результаты (суммирование значений или вычисление среднего) при завершении параллельной операции.
Решение
Для поддержки агрегирования класс
Parallel использует концепцию локальных значений — переменных, существующих локально внутри параллельного цикла. Это означает, что тело цикла может просто обратиться к значению напрямую, без необходимости синхронизации. Когда цикл готов к агрегированию всех своих локальных результатов, он делает это с помощью делегата localFinally. Следует отметить, что делегату localFinally нужно синхронизировать доступ к переменной для хранения результата. Пример параллельного суммирования:int ParallelSum(IEnumerable<int> values)В Parallel LINQ реализована более понятная поддержка агрегирования, чем в классе
{
int result = 0;
Parallel.ForEach(source: values,
localInit: () => 0,
body: (item, state, localValue) =>
localValue + item,
localFinally: localValue =>
Interlocked.Add(ref result, localValue)
);
return result;
}
Parallel:int ParallelSum(IEnumerable<int> values)На самом деле в PLINQ реализована встроенная поддержка многих распространённых операторов (например,
{
return values.AsParallel().Sum();
}
Sum). Также предусмотрена обобщённая поддержка агрегирования с оператором Aggregate:int ParallelSum(IEnumerable<int> values)Если вы уже используете класс
{
return values.AsParallel().Aggregate(
seed: 0,
func: (sum, item) => sum + item
);
}
Parallel, следует использовать его поддержку агрегирования. В остальных случаях поддержка в PLINQ, как правило, более выразительна, а код получается короче.UPD: Обновил пример ParallelSum примером из документации с использованием Interlocked.Add вместо lock.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 4.