Parallel.ForEach Ordered Execution

问题描述 投票:18回答:6

我正在尝试使用新的C#4.0 Parallel.ForEach函数在对象列表上执行并行函数。这是一个非常漫长的维护过程。我想让它按列表的顺序执行,以便我可以在前一点停止并继续执行。我该怎么做呢?

这是一个例子。我有一个对象列表:a1 to a100。这是当前的订单:

a1, a51, a2, a52, a3, a53...

我想要这个订单:

a1, a2, a3, a4...

我可以将一些对象无序运行,但只要我能在列表中找到一个点,我可以说在此之前所有对象都已运行。我阅读了并行编程csharp白皮书,但没有看到任何相关内容。在ParallelOptions课程中没有这个设置。

c#-4.0 foreach parallel-processing html-lists
6个回答
3
投票

如果您使用Parallel.Break来终止循环,那么您将被保证所有低于返回值的索引都将被执行。这几乎就是你能得到的。此处的示例使用For但ForEach具有类似的重载。

int n = ...
var result = new double[n];

var loopResult = Parallel.For(0, n, (i, loopState) =>
{
   if (/* break condition is true */)
   {
      loopState.Break();
      return;
   }
   result[i] = DoWork(i);
});

if (!loopResult.IsCompleted && 
        loopResult.LowestBreakIteration.HasValue)
{
   Console.WriteLine("Loop encountered a break at {0}", 
                      loopResult.LowestBreakIteration.Value);
}

在ForEach循环中,为每个分区中的每个元素在内部生成迭代索引。执行不按顺序执行,但在中断后您知道所有低于LowestBreakIteration的迭代都将完成。

取自“与Microsoft .NET的并行编程”http://parallelpatterns.codeplex.com/

可在MSDN上获得。见http://msdn.microsoft.com/en-us/library/ff963552.aspx。 “早退出循环”部分涵盖了这种情况。

另见:http://msdn.microsoft.com/en-us/library/dd460721.aspx


2
投票

做这样的事情:

int current = 0;
object lockCurrent = new object();

Parallel.For(0, list.Count, 
             new ParallelOptions { MaxDegreeOfParallelism = MaxThreads },
             (ii, loopState) => {
                    // So the way Parallel.For works is that it chunks the task list up with each thread getting a chunk to work on...
                    // e.g. [1-1,000], [1,001- 2,000], [2,001-3,000] etc...
                    // We have prioritized our job queue such that more important tasks come first. So we don't want the task list to be
                    // broken up, we want the task list to be run in roughly the same order we started with. So we ignore tha past in 
                    // loop variable and just increment our own counter.
                    int thisCurrent = 0;
                    lock (lockCurrent) {
                        thisCurrent = current;
                        current++;
                    }
                    dothework(list[thisCurrent]);
                 });

你可以看到当你打破并行for循环时,你将知道要执行的最后一个列表项,假设你让所有线程在破坏之前完成。我不是PLINQ或LINQ的忠实粉丝。老实说,我看不出编写LINQ / PLINQ如何导致可维护的源代码或可读性...... Parallel.For是一个更好的解决方案。


1
投票

作为备用建议,您可以记录已运行的对象,然后在恢复执行时过滤列表以排除已运行的对象。

如果需要在应用程序重新启动时保持持久性,则可以存储已执行对象的ID(我假设对象具有一些唯一标识符)。


1
投票

对于遇到此问题的任何其他人 - 如果您循环遍历数组或列表(而不是IEnumberable),则可以使用Parallel.Foreach的重载,该重载使元素索引也能保持原始顺序。

string[] MyArray; // array of stuff to do parallel tasks on 
string[] ProcessedArray = new string[MyArray.Length];
Parallel.ForEach(MyArray, (ArrayItem,loopstate,ArrayElementIndex) =>
{
    string ProcessedArrayItem = TaskToDo(ArrayItem);
    ProcessedArray[ArrayElementIndex] = ProcessedArrayItem;
});

1
投票

对于任何寻找简单解决方案的人,我发布了2个扩展方法(一个使用PLINQ,一个使用Parallel.ForEach)作为以下问题的答案的一部分:

Ordered PLINQ ForAll


-2
投票

不确定问题是否因为我的评论错误而改变了。 在这里进行了改进,基本上提醒并行作业在您的控制顺序之外运行。 打印10个数字可能会产生1,4,6,7,2,3,9,0。

如果你想停止你的程序并在以后继续。 类似的问题通常会在批处理工作负载中结束。 并记录所做的事情。 假设您必须检查10.000数字是否为素数。 你可以批量循环100,并有一个主要的log1,log2,log3 log1 = 0..99 LOG2 = 100..199 务必设置一些标记以了解批处理作业是否已完成。

这是一个通用的方法,因为问题也不是那么精确。

© www.soinside.com 2019 - 2024. All rights reserved.