订购PLINQ ForAll
关于PLINQ中的订单保存的msdn文档说明了关于ForAll()
的以下内容。
- 订购源序列时的结果: 并行执行非确定性
- 源序列无序时的结果: 并行执行非确定性
这是否意味着永远不能保证ForAll
方法的有序执行?
我之前没有使用PLINQ,但以下Code Review问题似乎是适合它的用法。 在我的回答的底部,我写道:
Events.AsParallel().AsOrdered().ForAll( eventItem => { ... } );
阅读文档后,我相信AsOrdered()
不会改变任何东西?
我也怀疑以前的查询不能替换顺序很重要的简单for
循环?
可能还会发生对StringBuilder
并行调用,导致输出错误?
订单保留通常仅适用于结果 – 即输入可以按任何顺序处理 ,但以原始顺序返回 。
因为ForAll
没有返回任何东西,它实际上没有任何我所知道的效果。
对处理进行排序的唯一方法是在处理第1项之前,在处理第2项之前完成第0项……此时你没有并行性。
正如其他人正确回答的那样, ForAll
方法永远不能保证以任何特定顺序执行可枚举元素的操作,并将静默忽略AsOrdered()
方法调用。
为了使读者有充分的理由以一种与原始顺序保持一致的方式执行可枚举元素的操作(在并行处理上下文中是合理的),下面的扩展方法可能有所帮助。
public static void ForAllInApproximateOrder(this ParallelQuery source, Action action) { Partitioner.Create( source ) .AsParallel() .AsOrdered() .ForAll( e => action( e ) ); }
然后可以按如下方式使用:
orderedElements.AsParallel() .ForAllInApproximateOrder( e => DoSomething( e ) );
应该注意的是,上面的扩展方法使用PLINQ ForAll
而不是Parallel.ForEach
,因此inheritance了PLINQ内部使用的线程模型(这与Parallel.ForEach
使用的不同 – 默认情况下我的经验不那么激进)。 下面是使用Parallel.ForEach
类似扩展方法。
public static void ForEachInApproximateOrder(this ParallelQuery source, Action action) { source = Partitioner.Create( source ) .AsParallel() .AsOrdered(); Parallel.ForEach( source , e => action( e ) ); }
然后可以按如下方式使用:
orderedElements.AsParallel() .ForEachInApproximateOrder( e => DoSomething( e ) );
使用上述任何一种扩展方法时,无需将AsOrdered()
到您的查询,无论如何都会在内部调用它。
我发现这些方法可用于处理具有粗粒度重要性的元素。 例如,处理从最旧的开始并向最新的方向工作的记录可能是有用的。 在许多情况下,不需要记录的确切顺序 – 只要旧记录通常在较新记录之前得到处理。 类似地,可以处理具有低/中/高优先级的记录,使得在大多数情况下优先级较低的记录之前处理高优先级记录,边缘情况不远。
AsOrdered()
不会改变任何东西 – 如果你想对并行查询的结果强制执行顺序,你可以简单地使用foreach()
ForAll()
来利用并行性 ,这意味着对多个执行副作用集合中的项目一次。 事实上,排序仅适用于查询的结果(结果集合中项目的顺序),但这与ForAll()
无关,因为ForAll()
根本不影响订单。
在PLINQ中,目标是在保持正确性的同时最大化性能。 查询应尽可能快地运行,但仍会产生正确的结果。 在某些情况下,正确性要求保留源序列的顺序
请注意, ForAll()
不会转换集合(它不是投影到新集合),它纯粹是为了对PLINQ查询的结果执行副作用。
这是否意味着永远不能保证ForAll方法的有序执行?
是的 – 订单无法保证。
并行化意味着将工作分配给不同的线程,然后将它们的单独输出组合起来。
如果您需要订购输出,请不要使用PLinq – 或者添加一些后续步骤以重新排序。
此外,如果您正在从plinq执行中访问StringBuilder之类的对象,那么请确保这些对象是线程安全的 – 并且还要注意这个线程安全实际上可能使plinq比非并行linq慢。