Parallel.ForEach不能直接替代foreach,因线程不安全、竞态条件、I/O误用、小数据开销大等问题易致异常、丢数据或更慢;应据任务类型选型,并注意集合线程安全与并行度控制。

Parallel.ForEach 不能直接替代 foreach,用错会丢数据、抛异常、甚至比单线程还慢。
为什么 Parallel.ForEach 一跑就报“集合已修改”异常
最常见的错误是多个线程同时往同一个 List<t></t> 或 Dictionary<k></k> 里 Add。.NET 的非并发集合类不是线程安全的,Parallel.ForEach 默认并行执行,没有自动加锁。
- 别在循环体里直接调用
results.Add(item)—— 改用ConcurrentBag<t></t>、ConcurrentQueue<t></t>或lock保护 - 如果只是累加数字,优先用
Parallel.ForEach的重载 +ParallelOptions配合局部状态(localInit/localFinally),避免争抢 - 调试时加个
Thread.Sleep(1)反而更容易复现问题——这不是巧合,是暴露了竞态条件
Parallel.ForEach 和 ForEachAsync 到底该选哪个
看操作类型:Parallel.ForEach 专为 CPU 密集型任务设计(比如图像像素计算、数值模拟);I/O 操作(HTTP 请求、文件读写、数据库查询)必须用 Task.WhenAll + ForEachAsync(或手动 async/await)。
- 用
Parallel.ForEach发 100 个 HTTP 请求:线程池被占满,大量线程阻塞在等待响应上,吞吐暴跌 -
Parallel.ForEach的MaxDegreeOfParallelism控制的是线程数,不是并发请求数;而ForEachAsync控制的是异步任务并发度,更贴合 I/O 场景 - 混合场景(比如先解密再上传)要拆开:CPU 部分用
Parallel.ForEach,I/O 部分用await
如何避免 Parallel.ForEach 跑得比 foreach 还慢
并行有开销:线程调度、数据分区、结果聚合。小数据量或简单逻辑下,并行反而拖后腿。
- 实测阈值因机器而异,但一般
source.Count 且每项耗时 Parallel.ForEach - 用
ParallelOptions.MaxDegreeOfParallelism = Environment.ProcessorCount是合理起点,设成100不会变快,只会增加上下文切换 - 源集合要是支持高效随机访问的(
IList<t></t>或数组),否则Parallel.ForEach内部会先转成数组——隐式内存分配+拷贝
一个安全可用的 Parallel.ForEach 模板
以下代码处理一批字符串,清洗后存入线程安全集合,同时避免常见陷阱:
var source = File.ReadAllLines("data.txt");
var results = new ConcurrentBag<string>();
Parallel.ForEach(source, new ParallelOptions { MaxDegreeOfParallelism = 4 }, line =>
{
var cleaned = line.Trim().ToUpperInvariant();
if (!string.IsNullOrEmpty(cleaned))
results.Add(cleaned); // ConcurrentBag.Add 是线程安全的
});
// 注意:ConcurrentBag 遍历时顺序不保证,如需有序请转 List 后 Sort 或用其他结构
真正难的不是写出来,而是判断「这里到底要不要并行」——数据规模、操作性质、目标环境(容器?低配 VM?)都得一起看。漏掉任一环,Parallel.ForEach 就从加速器变成定时炸弹。


















