用
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或略高,避免上下文切换开销 特别耗内存的任务(如图像处理),适当调低更稳
提前退出和异常处理
不能用
break或
return直接跳出整个循环,但可以: 调用
loopState.Break():通知后续分区尽快停止(已开始的仍会完成) 调用
loopState.Stop():尽量立即中止所有未开始的迭代 异常会被包装成
AggregateException,需
try-catch并遍历
.InnerExceptions
例如查找第一个满足条件的元素:
int? found = null;
Parallel.ForEach(numbers, (n, loopState) => {
if (n > 50000) {
found = n;
loopState.Stop(); // 不再启动新任务
}
});
基本上就这些。用对了能快几倍,用错了反而更慢或出错——核心就三条:无共享写、合理控并发、异常要拆包。
