订购PLINQ ForAll

时间:2011-03-18 13:16:38

标签: c# plinq

关于order preservation in PLINQ的msdn文档说明了以下ForAll()

  • 源序列有序时的结果:并行执行非确定性
  • 源序列无序时的结果:并行执行非确定性

这是否意味着永远无法保证ForAll方法的有序执行?

之前我没有使用PLINQ,但以下Code Review question似乎是适合它的用法。在我的回答的底部,我写道:

Events.AsParallel().AsOrdered().ForAll( eventItem =>
{
    ...
} );    

阅读完文档之后,我相信AsOrdered()不会改变任何内容吗?我也怀疑之前的查询无法取代顺序很重要的简单for循环?
也可能会发生对StringBuilder的并行调用,导致输出错误?

6 个答案:

答案 0 :(得分:15)

订单保存通常仅适用于结果 - 即输入可以按任何顺序处理,但以原始顺序返回

由于ForAll没有返回任何内容,因此我没有发现任何影响。

处理进行排序的唯一方法是在处理第1项之前,在处理第2项之前完成第0项......此时你没有并行性。< / p>

答案 1 :(得分:7)

正如其他人正确回答的那样,ForAll方法永远不能保证以任何特定顺序执行可枚举元素的操作,并且会默默地忽略AsOrdered()方法调用。

为了让读者有充分理由以一种与原始顺序保持一致的方式对可枚举元素执行操作(在并行处理上下文中合理的话),下面的扩展方法可能有所帮助。 / p>

public static void ForAllInApproximateOrder<TSource>(this ParallelQuery<TSource> source, Action<TSource> action) {

    Partitioner.Create( source )
               .AsParallel()
               .AsOrdered()
               .ForAll( e => action( e ) );

}

然后可以按如下方式使用:

orderedElements.AsParallel()
               .ForAllInApproximateOrder( e => DoSomething( e ) );

应该注意的是,上面的扩展方法使用了PLINQ ForAll而不是Parallel.ForEach,因此继承了PLINQ内部使用的线程模型(与Parallel.ForEach使用的不同 - - 根据我的经验,默认情况下不那么激进)。使用Parallel.ForEach的类似扩展方法如下。

public static void ForEachInApproximateOrder<TSource>(this ParallelQuery<TSource> source, Action<TSource> action) {

    source = Partitioner.Create( source )
                        .AsParallel()
                        .AsOrdered();

    Parallel.ForEach( source , e => action( e ) );

}

然后可以按如下方式使用:

orderedElements.AsParallel()
               .ForEachInApproximateOrder( e => DoSomething( e ) );

在使用上述任何一种扩展方法时,无需将AsOrdered()链接到您的查询,无论如何都会在内部调用它。

我发现这些方法可用于处理具有粗粒度重要性的元素。例如,处理从最旧的开始并向最新的方向工作的记录可能是有用的。在许多情况下,不需要记录的确切顺序 - 只要较旧的记录通常在较新的记录之前得到处理。同样,可以处理具有低/中/高优先级的记录,以便在大多数情况下优先级较低的记录之前处理高优先级记录,边缘情况不远。

答案 2 :(得分:5)

AsOrdered()不会改变任何内容 - 如果您想对并行查询的结果强制执行顺序,只需使用foreach() ForAll()就可以利用并行性,这意味着一次对集合中的多个项目执行副作用。事实上,排序仅适用于查询的结果(结果集合中的项目顺序),但这与ForAll()无关,因为ForAll()根本不会影响订单。< / p>

  

在PLINQ中,目标是最大化   保持表现   正确性。查询应该以   尽可能快但仍然产生   正确的结果。在某些情况下,   正确性要求保留源序列的顺序

请注意ForAll()不会转换集合(它不是投影到新集合),它纯粹是为了对PLINQ查询的结果执行副作用。

答案 3 :(得分:4)

  

这是否意味着永远无法保证ForAll方法的有序执行?

是 - 订单无法保证。

并行化意味着将工作分配给不同的线程,然后将它们的单独输出组合起来。

如果您需要订购输出,请不要使用PLinq - 或者添加一些后续步骤以重新排序。


此外,如果您正在从plinq执行中访问StringBuilder之类的对象,那么请确保这些对象是线程安全的 - 并且还要注意这个线程安全实际上可能使plinq比非并行linq慢。

答案 4 :(得分:1)

现在作为扩展方法:

它将在多个内核上处理,然后对结果进行排序,因此存在排序的开销。这是answer on benchmarking simple for vs parallel

 public static IEnumerable<T1> OrderedParallel<T, T1>(this IEnumerable<T> list, Func<T, T1> action)
    {
        var unorderedResult = new ConcurrentBag<(long, T1)>();
        Parallel.ForEach(list, (o, state, i) =>
        {
            unorderedResult.Add((i, action.Invoke(o)));
        });
        var ordered = unorderedResult.OrderBy(o => o.Item1);
        return ordered.Select(o => o.Item2);
    }

使用方式:

var result = Events.OrderedParallel(eventItem => ...);

希望这可以为您节省一些时间。

答案 5 :(得分:-1)

ForAll在多个线程中并行运行操作。在任何给定时刻,多个动作将同时运行,在这些情况下,“顺序”的概念不适用。要按顺序运行操作,必须按顺序运行它们,最简单的方法是在单个线程中运行它们。只需在标准foreach循环中枚举查询结果即可实现:

var query = Events.AsParallel().AsOrdered();
foreach (var eventItem in query)
{
    // do something with the eventItem
}

如果您喜欢流利的ForAll语法,则可以使用下面的ForEach扩展方法在项目中添加静态类:

public static void ForEach<TSource>(this IEnumerable<TSource> source,
    Action<TSource> action)
{
    foreach (TSource item in source)
    {
        action(item);
    }
}

并像这样使用它:

Events.AsParallel().AsOrdered().ForEach(eventItem =>
{
    // do something with the eventItem
});

但是应该注意,在给定的示例中,并行LINQ的使用是多余的。查询Events.AsParallel().AsOrdered()不会对可枚举的源进行任何转换,因此不会进行实际的计算。您可以删除.AsParallel().AsOrdered()部分并获得相同的结果。