C#怎么使用Parallel.ForEach C#并行循环加速处理

Parallel.ForEach可自动并行化CPU密集型任务,但需确保线程安全、避免共享状态、慎用于I/O场景;基本用法是替换foreach并使用System.Threading.Tasks;推荐用局部状态+合并方式处理聚合结果,配合ParallelOptions控制并发度,异常需捕获AggregateException并遍历InnerExceptions。

Parallel.ForEach 能让集合遍历自动并行化,显著提升 CPU 密集型任务的执行速度,但不是所有场景都适合——关键看是否线程安全、有无共享状态、是否 I/O 主导。

基本用法:替换普通 foreach

把原来的串行循环改成并行,只需两步:

  • 引用 System.Threading.Tasks
  • Parallel.ForEach(集合, item => { /* 处理逻辑 */ }) 替代 foreach

例如处理一个整数列表求平方:

(注意:下面代码不涉及共享变量,是安全的)

var numbers = Enumerable.Range(1, 100000).ToList();
Parallel.ForEach(numbers, n => {
    var result = n * n; // 纯计算,无副作用
    // 可以写入线程本地变量或加锁写入共享结构
});

避免共享资源冲突:别直接改公共变量

多个线程同时写同一个变量(如 sum += n)会导致结果错误。正确做法有:

  • Interlocked.Add(ref sum, n) 原子操作(适合简单数值累加)
  • lock(obj) 包裹临界区(开销略大,但通用)
  • Parallel.ForEach 的重载 + 局部状态(推荐,性能最好)

局部状态示例(每个线程独立累加,最后合并):

(适用于需要汇总结果的场景)

int total = Parallel.ForEach(
    numbers,
    () => 0, // 每个线程的局部初始值
    (n, loopState, localSum) => localSum + n * n, // 局部处理
    localSum => Interlocked.Add(ref total, localSum) // 合并到全局
);

控制并发度:防止线程过多拖慢性能

默认会根据 CPU 核心数动态调整线程数,但遇到大量小任务或内存敏感场景,可手动限制:

  • 传入 new ParallelOptions { MaxDegreeOfParallelism = 4 }
  • 一般设为 Environment.ProcessorCount 或略高,避免上下文切换开销
  • 特别耗内存的任务(如图像处理),适当调低更稳

提前退出和异常处理

不能用 breakreturn 直接跳出整个循环,但可以:

  • 调用 loopState.Break():通知后续分区尽快停止(已开始的仍会完成)
  • 调用 loopState.Stop():尽量立即中止所有未开始的迭代
  • 异常会被包装成 AggregateException,需 try-catch 并遍历 .InnerExceptions

例如查找第一个满足条件的元素:

int? found = null;
Parallel.ForEach(numbers, (n, loopState) => {
    if (n > 50000) {
        found = n;
        loopState.Stop(); // 不再启动新任务
    }
});

基本上就这些。用对了能快几倍,用错了反而更

慢或出错——核心就三条:无共享写、合理控并发、异常要拆包。