ForAll是PLINQ专属的无返回、不保序、不合并结果的并行消费方法,仅用于ParallelQuery末端;ForEach是IEnumerable/List的顺序遍历方法,ParallelQuery上不存在该方法。

ForAll 是 PLINQ 专属的并行消费方法,ForEach 是普通集合的顺序遍历
ForAll 只存在于 ParallelQuery(即调用 AsParallel() 后的查询结果),它不返回值、不保证执行顺序、也不合并结果——每个线程拿到自己的数据块后立刻执行委托,完事就退出。ForEach 则是 IEnumerable 或 List 上的实例方法,纯顺序执行,线程安全需自行保障,且会等全部元素处理完才返回。
-
ForAll不能链式返回新集合,只适合“发通知”“写日志”“更新非共享状态”这类无返回、无依赖的操作 -
ForEach在 PLINQ 中根本不存在——你写list.AsParallel().ForEach(...)会编译失败,因为ParallelQuery没有这个方法;真正能用的是Parallel.ForEach(...)(来自System.Threading.Tasks.Parallel),但那是另一套 API,和 LINQ 风格无关 - 别把
Parallel.ForEach和ParallelQuery.ForAll混为一谈:前者接受IEnumerable或分区器,后者只接受ParallelQuery
为什么 ForAll 不保证顺序?这和 PLINQ 的分区机制直接相关
PLINQ 把源集合切分成若干段(partition),分给不同线程处理。这些段大小不固定、分配时机不确定、完成时间也不同。ForAll 就是让每个线程在自己分到的那块数据上“立刻开干”,不做任何等待或排序协调——所以输出顺序完全不可预测。
- 如果你需要顺序输出(比如写入文件、生成有序报告),
ForAll不适用;该用foreach遍历ToArray()或ToList()结果 -
ForAll内部跳过结果合并步骤,因此比ToArray()+foreach快,尤其在数据量大、操作耗时长时优势明显 - 若委托里访问了共享变量(如静态计数器、全局 list),必须加锁或改用线程安全类型(如
ConcurrentBag),否则结果错乱
常见误用:想并行又想要顺序,结果既慢又错
典型错误是这样写:
numbers.AsParallel()
.Where(n => IsPrime(n))
.OrderBy(n => n) // 强制全缓冲 + 排序合并
.ForAll(Console.WriteLine); // 以为能按序打印素数
问题在于:OrderBy 会让 PLINQ 缓冲所有结果再排序,彻底抵消并行优势;而 ForAll 仍不保证输出顺序(即使输入已排序,多线程并发写控制台也会乱序)。
- 要顺序输出:去掉
ForAll,改用foreach (var x in query.OrderBy(...)) Console.WriteLine(x); - 要纯并行处理 + 丢弃结果:保留
ForAll,但删掉OrderBy等强制合并的运算符 - 想边算边处理?PLINQ 默认“部分缓冲”,可用
WithMergeOptions(ParallelMergeOptions.NotBuffered)让结果更早流出,但仍不保序
ForEach 方法名重复导致的认知陷阱
名字都叫 ForEach,但实际是三个不同东西:
-
List:实例方法,顺序,单线程,属于 .NET Framework 2.0 就有的老 API.ForEach() -
Parallel.ForEach():静态方法,接受IEnumerable或自定义分区器,可配置并行度、取消令牌等,属于 TPL -
ParallelQuery:扩展方法,仅用于 PLINQ 查询链末端,无返回、无合并、不保序.ForAll()
它们之间没有继承或重载关系,只是命名巧合。选哪个,取决于你手头的数据类型和目标:是已有集合想并行遍历?用 Parallel.ForEach;是 LINQ 查询想加速过滤+消费?用 AsParallel().Where(...).ForAll(...);只是简单循环打印?foreach 最稳。










