12

关于PLINQ 中订单保存的 msdn 文档说明了以下关于ForAll().

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

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

我以前没有使用过 PLINQ,但是下面的Code Review 问题似乎是它的合适用法。在我的答案的底部,我写道:

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

阅读文档后,我相信AsOrdered()不会改变任何东西?
我还怀疑前面的查询不能替换for顺序很重要的简单循环?可能还会发生
对 的并行调用,导致输出错误?StringBuilder

4

6 回答 6

16

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

因为ForAll不返回任何东西,所以它并没有我所知道的任何影响。

将排序应用于处理的唯一方法是在处理第 1 项之前完成第 0 项,在处理第 2 项之前等……此时您没有并行性。

于 2011-03-18T13:23:42.667 回答
8

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

为了读者有正当理由以接近原始顺序的方式执行可枚举元素的操作(只要在并行处理上下文中合理),下面的扩展方法可能会有所帮助。

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 ) );

应该注意的是,上面的扩展方法使用 PLINQForAll而不是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()使用上述任一扩展方法时,无需链接到您的查询,无论如何它都会在内部调用。

我发现这些方法在处理具有粗粒度意义的元素时很有用。例如,它可以用于处理从最旧开始到最新的记录。在许多情况下,不需要记录的确切顺序 - 只要较旧的记录通常在较新的记录之前得到处理。类似地,可以处理具有低/中/高优先级的记录,以便在大多数情况下,高优先级记录将在低优先级记录之前处理,边缘情况紧随其后。

于 2014-01-05T01:42:46.450 回答
6

AsOrdered()不会改变任何东西——如果你想对并行查询的结果强制执行顺序,你可以简单地使用foreach() ForAll()它来利用并行性,这意味着一次对集合中的多个项目执行副作用。实际上排序只适用于查询的结果(结果集合中项目的顺序),但这与 无关ForAll(),因为ForAll()根本不影响顺序。

在 PLINQ 中,目标是在保持正确性的同时最大化性能。查询应尽可能快地运行,但仍会产生正确的结果。在某些情况下,正确性需要保留源序列的顺序

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

于 2011-03-18T13:22:54.933 回答
4

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

是的 - 不保证订单。

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

如果您需要订购输出,则不要使用 PLinq - 或添加一些稍后的步骤以重新订购。


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

于 2011-03-18T13:22:18.477 回答
2

现在作为扩展方法:

它将在多个核心上处理,然后对结果进行排序,因此存在排序的开销。这是一个简单的基准测试的答案 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 => ...);

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

于 2020-03-14T10:45:26.083 回答
-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()部分并获得相同的结果。

于 2020-03-14T12:29:35.027 回答