C# 如何在查询完成之前按顺序开始处理并行查询的结果?
我有一个字符串集合,需要对其执行两个操作 其中第一个可以安全地以任何顺序(yay)独立处理,但随后必须以原始顺序顺序(boo)处理输出 下面的Plinq让我了解了大部分情况:C# 如何在查询完成之前按顺序开始处理并行查询的结果?,c#,.net,.net-4.0,plinq,C#,.net,.net 4.0,Plinq,我有一个字符串集合,需要对其执行两个操作 其中第一个可以安全地以任何顺序(yay)独立处理,但随后必须以原始顺序顺序(boo)处理输出 下面的Plinq让我了解了大部分情况: myStrings.AsParallel().AsOrdered() .Select( str => Operation1(str) ) .AsSequential() .Select( str => Operation2(str) ); //immagin
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(),首先对每个字符串执行操作1,然后将结果元素排序回其原始顺序,最后开始执行操作2
理想情况下,只要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[0]从操作1中出来,操作2就会开始
我想知道的是:
- 没有对.WithMergeOptions(ParallelMergeOptions.NotBuffered)的调用,Operation2直到所有Operation1调用都已启动才开始工作(这比等待它们全部完成的原始代码要好)
- 现实生活中的问题:
Operation1正在大量文本中搜索法律引文和参考文献(例如:“1989年儿童法案”)。
这些引用通常是独立的,但有时抄本会包含类似“上述法案第6节”的内容。 Operation2依赖于Operation1中的捕获来获取这些部分引用
信号灯
,即可防止过早访问“文章ID”
并行准备、处理和恢复(无生成)的完整代码为:
类文本{
公共静态正则表达式rx=新正则表达式(@“(PREVID | ACTID\=([0-9]+)”);
私有文本prv;//上一篇文章
私有字符串ot;//原始文本
private int id;//对文本执行操作id
私有信号量isComputed=新信号量(0,1);
公共国际活动{
得到{
isComputed.WaitOne();
int _id=id;
isComputed.Release();
返回_id;
}
}
公共bool ProcessText(){
var mx=接收匹配(ot);
var prev=mx.Groups[1]。值==“PREVID”;
如果(上一个)
id=prv==null?0:prv.ActID;
其他的
如果(!int.TryParse(mx.Groups[2].值,out id))
抛出新异常(string.Format(@“不正确的项目id”{0}“”,mx.Groups[0].Value));
isComputed.Release();
返回!上一个;
}
公共文本(字符串原始文本,文本前一个){
prv=以前的;
ot=原始文本;
}
}
公共静态void Main(字符串[]args){
//相同的随机流(用于调试)
var rnd=新的随机变量(1);
var noise=@“这些引用通常是独立的,但偶尔会出现”;
//一些杂音文字
变量位=新函数(()=>
noise.Substring(0,rnd.Next(noise.Length));
//随机文章
变量文本=新函数(()=>
string.Format(@“{0}{1}{2}”,bit(),
rnd.Next()%2==0?“上一个ID”
:string.Format(@“ACTID={0}”、rnd.Next()、bit());
//随机数据输入
var data=新列表();
文本prv=null;
对于(变量n=0;n<1000000;n++)
//生产者/消费者最好并行化加载数据步骤
data.Add(prv=新文本(Text(),prv));
控制台。写入(“按键启动…”);
Console.ReadKey();
//并行处理
Console.WriteLine(“{0}唯一ID”),data.AsParallel().Where(n=>n.ProcessText()).Count());
Console.WriteLine(“进程已完成”);
}
如您所见,ProcessText
并行处理所有文章。只有PREVID文章会等到他们的前一篇文章计算他们自己的id
抽象这种行为(我认为)的真正问题是项关系(一项依赖于另一项),在Linq中,自然的方式是“无项关系”(必须使用“分组方式”来执行)
我建议您使用生产者/消费者模式。不仅仅是使用
并行合并选项。NotBuffered
(没有字典)提供您想要的吗?似乎没有(除非我做得不正确)——只是尝试MyStrings.AsParallel().AsOrdered()。使用合并选项(ParallelMergeOptions.NotBuffered)。选择(…)在并行查询完成之前,似乎仍会阻塞。Operation2(…)的初始语句中的“AsSequential()”是什么?Operation2(…)需要按顺序一次传递所有字符串,因此AsSequential()用于防止第二个Select并行执行(否则它会这样做)。确定。让我问以下问题:据我所知,经过运算1和运算2的字符串数是相同的。所以您正在将初始序列拆分为proc
myStrings.AsParallel()
.SelectAsOrdered( str => Operation1(str) )
.Select(str => Operation2(str));
B = f(A)
class Text {
public static Regex rx = new Regex(@" (PREVID|ACTID\=([0-9]+)) ");
private Text prv; // previous article
private string ot; // original text
private int id; // act id on text
private Semaphore isComputed = new Semaphore(0, 1);
public int ActID {
get {
isComputed.WaitOne();
int _id = id;
isComputed.Release();
return _id;
}
}
public bool ProcessText() {
var mx = rx.Match(ot);
var prev = mx.Groups [1].Value == "PREVID";
if(prev)
id = prv == null ? 0 : prv.ActID;
else
if(!int.TryParse(mx.Groups [2].Value, out id))
throw new Exception(string.Format(@"Incorrect article id ""{0}""", mx.Groups [0].Value));
isComputed.Release();
return !prev;
}
public Text(string original_text, Text previous) {
prv = previous;
ot = original_text;
}
}
public static void Main(String [] args) {
// same random stream (for debugging)
var rnd = new Random(1);
var noise = @"These references are usually independent, but occasionally";
// some noise text
var bit = new Func<string>(() =>
noise.Substring(0, rnd.Next(noise.Length)));
// random article
var text = new Func<string>(() =>
string.Format(@"{0}{1}{2}", bit(),
rnd.Next() % 2 == 0 ? " PREVID "
: string.Format(@" ACTID={0} ", rnd.Next()), bit()));
// random data input
var data = new List<Text>();
Text prv = null;
for(var n = 0; n < 1000000; n++)
// producer / consumer is better to parallelize load data step
data.Add(prv = new Text(text(), prv));
Console.Write("Press key to start...");
Console.ReadKey();
// parallel processing
Console.WriteLine("{0} unique ID's", data.AsParallel().Where(n => n.ProcessText()).Count());
Console.WriteLine("Process completed.");
}