parallel.for适用于cpu密集型可分段循环,要求无共享状态;task.whenall用于并发i/o操作,需控并发数;二者不可混用async;复杂流水线应选tpl dataflow。

Parallel.For 适合 CPU 密集型、可分段的循环
它把一个 for 循环自动拆成多个分区,交给线程池里的线程并行执行,但要求循环体不共享可变状态,也不能依赖执行顺序。
- 常见错误现象:
IndexOutOfRangeException或结果错乱——因为多个线程同时改同一个数组/列表,没加锁也没隔离 - 使用场景:图像像素处理、数值计算(如矩阵每行求和)、批量解析固定长度数据块
- 参数差异:
Parallel.For(0, 1000, i => { ... })的i是每个线程独享的局部变量,不是共享计数器;想累积结果得用Parallel.For(..., () => ..., (i, state, local) => ..., final => ...)的局部状态重载 - 性能影响:小循环(比如不到 100 次)开销可能大于收益;默认使用
ThreadPool,若任务阻塞(如等 I/O),会拖慢其他并行任务
Task.WhenAll 适合并发发起多个独立异步操作
它不并行执行代码,而是等待一组 Task 全部完成,常用于 HTTP 请求、文件读取、数据库查询这类 I/O 绑定操作。
- 常见错误现象:直接
await Task.WhenAll(list.Select(x => DoSomethingAsync(x)))却没控制并发数,导致连接池耗尽或服务端限流(如HttpRequestException: Too many open connections) - 使用场景:同时拉多个 API、并发下载一批文件、批量写入不同数据库表
- 参数差异:传入的是
IEnumerable<task></task>,不是Task<t>[]</t>;如果其中任一Task抛异常,Task.WhenAll返回的Task也会以AggregateException形式抛出,需用await后 try/catch 或检查task.Exception - 兼容性影响:.NET 5+ 支持
Task.WhenAll(params Task[])和泛型重载;旧版本注意别传null或空集合,否则返回已完成但未设置结果的Task
混用 Parallel.For 和 async/await 会掉进坑里
Parallel.For 是同步并行,内部不识别 async 委托——你写 async i => await FooAsync(),编译器会强制转成 Func<int task></int>,而 Parallel.For 只接受 Action<int></int>,根本没法编译通过。
- 典型错误写法:
Parallel.For(0, n, async i => await LoadDataAsync(i))→ 编译失败,提示委托类型不匹配 - 正确替代方案:用
Task.WhenAll+Enumerable.Range生成任务列表,再并发执行;若要限制并发数,套一层SemaphoreSlim - 性能陷阱:有人用
Task.Run(() => { Parallel.ForEach(...); })包一层假装“异步”,实际只是把 CPU 密集工作扔给线程池,没解决 I/O 阻塞问题,还多一层调度开销
什么时候该选 TPL Dataflow 而不是硬凑 Parallel.For + Task.WhenAll
当你的并行逻辑涉及“生产-处理-消费”链条、需要缓冲、节流、条件转发,或者中间步骤有状态(比如累计统计、去重缓存),硬用 Parallel.For 或 Task.WhenAll 就会越来越难维护。
- 典型信号:
ConcurrentQueue+ 手动Task.Run+while(true)循环消费 → 其实就是重复造TransformBlock的轮子 - 使用场景:实时日志解析(过滤→提取→聚合)、ETL 流水线、消息预处理管道
- 关键配置项:
MaxDegreeOfParallelism控制并发数,BoundedCapacity防内存暴涨,EnsureOrdered = false提升吞吐(默认是 true) - 容易被忽略的点:块之间链接后,上游
Complete()不代表下游自动完成,必须显式调用Completion.ContinueWith(...)或等OutputAvailableAsync()











