【发布时间】:2012-10-15 04:59:52
【问题描述】:
我有一个字符串集合,我需要对其执行两个操作。
其中的第一个可以安全地以任何顺序独立处理 (yay),但输出必须按原始顺序依次处理 (boo)。
下面的 Plinq 让我大部分时间到达那里:
myStrings.AsParallel().AsOrdered()
.Select( str => Operation1(str) )
.AsSequential()
.Select( str => Operation2(str) );
//immagine Operation2() maintains some sort of state and must take the outputs from Operation1 in the original order
这让我了解了大部分情况,但问题是由于 AsOrdered(),Operation1 首先在每个字符串上执行,然后将结果元素排序回原来的顺序,最后 Operation2 开始执行。
理想情况下,只要 Operation1 调用返回第一个字符串(即 myStrings[0],而不是返回的第一个),我希望 Operation2 开始它的工作。
所以这是我尝试一般性地解决问题:
public static class ParallelHelper
{
public static IEnumerable<U> SelectAsOrdered<T, U>(this ParallelQuery<T> query, Func<T, U> func)
{
var completedTasks = new Dictionary<int, U>();
var queryWithIndexes = query.Select((x, y) => new { Input = x, Index = y })
.AsParallel()
.Select(t => new { Value = func(t.Input), Index = t.Index })
.WithMergeOptions(ParallelMergeOptions.NotBuffered);
int i = 0;
foreach (var task in queryWithIndexes)
{
if (i==task.Index)
{
Console.WriteLine("immediately yielding task: {0}", i);
i++;
yield return task.Value;
U previouslyCompletedTask;
while (completedTasks.TryGetValue(i, out previouslyCompletedTask))
{
completedTasks.Remove(i);
Console.WriteLine("delayed yielding task: {0}", i);
yield return previouslyCompletedTask;
i++;
}
}
else
{
completedTasks.Add(task.Index, task.Value);
}
}
yield break;
}
}
然后我可以将我原来的代码块重写为:
myStrings.AsParallel()
.SelectAsOrdered( str => Operation1(str) )
.Select(str => Operation2(str));
一旦 myStrings[0] 从 Operation1 出来,Operation2 就会启动。
我想知道的是:
- 这是并行化中相当常见的问题/模式,我是否遗漏了一些在 .Net 框架中开箱即用的东西?还是有更简单的方法?
- 虽然上述扩展方法似乎可以完成这项工作,但如何改进?代码中的任何内容看起来都是个坏主意吗?
谢谢!
安迪
以防万一您有兴趣:
如果不调用 .WithMergeOptions(ParallelMergeOptions.NotBuffered),Operation2 直到所有 Operation1 调用都开始后才会开始工作(这比等待它们全部完成的原始代码要好)。
现实生活中的问题:
Operation1 正在大量文本中搜索法律引用和参考文献(例如:“1989 年儿童法案”)。
这些参考文献通常是独立的,但有时抄本会包含类似“前面提到的法案的第 6 节”之类的内容。 Operation2 依赖于 Operation1 的捕获来获取这些部分引用。
【问题讨论】:
-
不只是使用
ParallelMergeOptions.NotBuffered(没有字典)给你想要的吗? -
似乎没有(除非我做错了)——只是尝试 MyStrings.AsParallel().AsOrdered().WithMergeOptions(ParallelMergeOptions.NotBuffered).Select(...) 仍然似乎一直阻塞,直到并行查询完成。
-
初始语句中的“AsSequential()”是做什么用的?
-
Operation2(...) 需要依次传递所有字符串,因此 AsSequential() 可以防止第二个 Select 并行执行(否则它会做)。
-
好的。让我问以下问题:据我所知,通过 Operation1 和 Operation2 的字符串数量是相同的。因此,您正在拆分初始序列以并行处理它,然后合并并按顺序处理。为什么您认为完全可以通过并行化流程获得任何性能提升?我的感觉是,在您的用例中使用并行化意味着只是开销。