parallel适用于cpu密集型循环,task.whenall适用于并发i/o操作;混用会导致编译失败、结果错乱或连接池耗尽。

Parallel 适合 CPU 密集型循环,Task.WhenAll 适合并发 I/O 操作——混用或误用会导致编译失败、结果错乱、连接池耗尽。
Parallel.For 编译报错:async 委托不被接受
你写 Parallel.For(0, n, async i => await LoadDataAsync(i)),编译器直接拒绝:委托类型不匹配。因为 Parallel.For 只接受 Action<int></int>,而 async i => ... 实际生成的是 Func<int task></int>,根本不是同一类型。
- 这不是“语法糖没开好”,是设计层面的隔离:Parallel 是同步并行模型,不感知
await - 若硬要并行 + 异步,必须换路子:用
Task.WhenAll配合Enumerable.Range生成任务列表 - 小数据量(比如 Parallel.For 反而更慢——线程调度开销压过计算收益
Task.WhenAll 并发爆炸导致 HttpRequestException
常见错误是 await Task.WhenAll(items.Select(x => HttpCallAsync(x))),没加限流,瞬间发起几百个 HTTP 请求,服务端返回 HttpRequestException: Too many open connections,本地也可能触发连接池耗尽。
- 默认不限并发数,.NET 的
HttpClient连接池有硬上限(通常 10–100,取决于配置) - 正确做法:用
SemaphoreSlim包一层,或改用Parallel.ForEachAsync(.NET 6+) - 别传空集合给
Task.WhenAll:.NET 5 之前会返回未完成的Task,.NET 6+ 已修复,但旧项目仍需判空
Parallel.ForEach 写入共享列表时结果错乱
代码像这样:var results = new List<int>(); Parallel.ForEach(data, x => results.Add(x * x));</int>,运行后 results.Count 小于预期,甚至抛 IndexOutOfRangeException。
-
List<t></t>不是线程安全的,Add可能同时修改内部数组和计数器 - 简单替换为
ConcurrentBag<t></t>或ConcurrentQueue<t></t>即可,但注意它们不保证顺序 - 若需保序且高性能,优先用局部状态重载:
Parallel.ForEach(..., () => new List<int>(), (x, state, local) => local.Add(...), local => lock(...) { results.AddRange(local); })</int>
ParallelOptions.MaxDegreeOfParallelism 设成 1 就等于串行?
设成 1 确实只用一个线程,但不代表“退化成普通 for 循环”——它仍有额外开销:分区逻辑、状态协调、异常包装(AggregateException),还可能因线程池调度延迟反而更慢。
- 真想串行执行,就别用
Parallel,直接写for或foreach -
MaxDegreeOfParallelism主要用于压制资源争用,比如在 I/O 密集场景下避免线程池饥饿,或限制 CPU 占用率 - 设为
-1表示不限制(默认行为),但实际并发数仍受系统线程池容量和当前负载影响
真正难的不是调哪个 API,而是判断「这活该不该并行」:CPU 密集?I/O 密集?数据是否独立?结果是否需要顺序?锁的成本比并行收益还高吗?这些问题没理清,代码写得再“高级”,也只会让问题更隐蔽。











