c# ForAll 和 ForEach 在PLINQ中的区别

来源:这里教程网 时间:2026-02-21 17:36:30 作者:

ForAll 是 PLINQ 专属的并行消费方法,ForEach 是普通集合的顺序遍历

ForAll
只存在于
ParallelQuery<t></t>
(即调用
AsParallel()
后的查询结果),它不返回值、不保证执行顺序、也不合并结果——每个线程拿到自己的数据块后立刻执行委托,完事就退出。
ForEach
则是
IEnumerable<t></t>
List<t></t>
上的实例方法,纯顺序执行,线程安全需自行保障,且会等全部元素处理完才返回。

ForAll
不能链式返回新集合,只适合“发通知”“写日志”“更新非共享状态”这类无返回、无依赖的操作
ForEach
在 PLINQ 中根本不存在——你写
list.AsParallel().ForEach(...)
会编译失败,因为
ParallelQuery<t></t>
没有这个方法;真正能用的是
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<t></t>
),否则结果错乱

常见误用:想并行又想要顺序,结果既慢又错

典型错误是这样写:

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<t>.ForEach()</t>
:实例方法,顺序,单线程,属于 .NET Framework 2.0 就有的老 API
Parallel.ForEach()
:静态方法,接受
IEnumerable
或自定义分区器,可配置并行度、取消令牌等,属于 TPL
ParallelQuery<t>.ForAll()</t>
:扩展方法,仅用于 PLINQ 查询链末端,无返回、无合并、不保序

它们之间没有继承或重载关系,只是命名巧合。选哪个,取决于你手头的数据类型和目标:是已有集合想并行遍历?用

Parallel.ForEach
;是 LINQ 查询想加速过滤+消费?用
AsParallel().Where(...).ForAll(...)
;只是简单循环打印?
foreach
最稳。

相关推荐

热文推荐