C# TPL数据流,JoinBlock限制的替代方案?
我寻找JoinBlock的替代品,它可以通过n-TransformBlocks链接,并将所有TransformBlock源块的消息连接/合并在一起,以便将此类消息的集合传递给另一个数据流块 JoinBlock可以很好地完成这项工作,但它仅限于连接3个源块。它还存在相当多的低效率问题(连接两个源块的偶数值类型(Int)的速度非常慢)。有没有办法让任务从TransformBlocks返回,并等到所有TransformBlocks都有一个完成的任务要传递,然后再接受C# TPL数据流,JoinBlock限制的替代方案?,c#,concurrency,task-parallel-library,tpl-dataflow,actor-model,C#,Concurrency,Task Parallel Library,Tpl Dataflow,Actor Model,我寻找JoinBlock的替代品,它可以通过n-TransformBlocks链接,并将所有TransformBlock源块的消息连接/合并在一起,以便将此类消息的集合传递给另一个数据流块 JoinBlock可以很好地完成这项工作,但它仅限于连接3个源块。它还存在相当多的低效率问题(连接两个源块的偶数值类型(Int)的速度非常慢)。有没有办法让任务从TransformBlocks返回,并等到所有TransformBlocks都有一个完成的任务要传递,然后再接受任务 有其他的想法吗?我可能有1-2
任务
有其他的想法吗?我可能有1-20个这样的转换块,在传递连接的项集合之前,我需要将这些项连接在一起。每个转换块保证为每个输入项“转换”返回一个输出项
编辑:要求澄清:
根据我之前的一个问题,我将JoinBlocks设置如下:
public Test()
{
broadCastBlock = new BroadcastBlock<int>(i =>
{
return i;
});
transformBlock1 = new TransformBlock<int, int>(i =>
{
return i;
});
transformBlock2 = new TransformBlock<int, int>(i =>
{
return i;
});
joinBlock = new JoinBlock<int, int>();
processorBlock = new ActionBlock<Tuple<int, int>>(tuple =>
{
//Console.WriteLine("tfb1: " + tuple.Item1 + "tfb2: " + tuple.Item2);
});
//Linking
broadCastBlock.LinkTo(transformBlock1, new DataflowLinkOptions { PropagateCompletion = true });
broadCastBlock.LinkTo(transformBlock2, new DataflowLinkOptions { PropagateCompletion = true });
transformBlock1.LinkTo(joinBlock.Target1);
transformBlock2.LinkTo(joinBlock.Target2);
joinBlock.LinkTo(processorBlock, new DataflowLinkOptions { PropagateCompletion = true });
}
public void Start()
{
Stopwatch watch = new Stopwatch();
watch.Start();
const int numElements = 1000000;
for (int i = 1; i <= numElements; i++)
{
broadCastBlock.Post(i);
}
////mark completion
broadCastBlock.Complete();
Task.WhenAll(transformBlock1.Completion, transformBlock2.Completion).ContinueWith(_ => joinBlock.Complete());
processorBlock.Completion.Wait();
watch.Stop();
Console.WriteLine("Time it took: " + watch.ElapsedMilliseconds + " - items processed per second: " + numElements / watch.ElapsedMilliseconds * 1000);
Console.ReadLine();
}
公共测试()
{
broadCastBlock=新的broadCastBlock(i=>
{
返回i;
});
transformBlock1=新的TransformBlock(i=>
{
返回i;
});
transformBlock2=新的TransformBlock(i=>
{
返回i;
});
joinBlock=新的joinBlock();
processorBlock=新动作块(元组=>
{
//Console.WriteLine(“tfb1:+tuple.Item1+”tfb2:+tuple.Item2);
});
//连接
broadCastBlock.LinkTo(transformBlock1,新的DataflowLinkOptions{PropagateCompletion=true});
broadCastBlock.LinkTo(transformBlock2,新的DataflowLinkOptions{PropagateCompletion=true});
transformBlock1.LinkTo(joinBlock.Target1);
transformBlock2.LinkTo(joinBlock.Target2);
LinkTo(processorBlock,新数据流链接选项{PropagateCompletion=true});
}
公开作废开始()
{
秒表=新秒表();
watch.Start();
常数整数=1000000;
for(int i=1;i joinBlock.Complete());
processorBlock.Completion.Wait();
看,停;
Console.WriteLine(“花费的时间:“+watch.elapsedmillesons+”-每秒处理的项目:“+numElements/watch.elapsedmillesons*1000”);
Console.ReadLine();
}
一种方法是使用BatchBlock
并将Greedy
设置为false
。在此配置中,块不会执行任何操作,直到来自n
不同块的n
项等待使用(其中n
是创建BatchBlock
时设置的数字)。当这种情况发生时,它一次消耗所有n
项,并生成一个包含所有项的数组
此解决方案的一个警告是,生成的数组没有排序:您不知道哪个项来自哪个源。我不知道它的性能与
JoinBlock
相比如何,您必须自己进行测试。(虽然我可以理解,如果以这种方式使用BatchBlock
会比较慢,因为非贪婪消费需要开销。)如果您想对每个项目执行多个并行操作,那么在单个块内执行这些操作更为合理,而不是将它们拆分为多个块,然后再次尝试将独立结果合并到单个对象中。所以我的建议是这样做:
var block = new TransformBlock<MyClass, MyClass>(async item =>
{
Task<SomeType1> task1 = Task.Run(() => CalculateProperty1(item.Id));
Task<SomeType2> task2 = Task.Run(() => CalculateProperty2(item.Id));
await Task.WhenAll(task1, task2).ConfigureAwait(false);
item.Property1 = task1.Result;
item.Property2 = task2.Result;
return item;
}, new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 2
});
var block=newtransformblock(异步项=>
{
Task task1=Task.Run(()=>CalculateProperty1(item.Id));
tasktask2=Task.Run(()=>CalculateProperty2(item.Id));
wait Task.WhenAll(task1,task2).configurewait(false);
item.Property1=task1.结果;
item.Property2=任务2.结果;
退货项目;
},新的ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism=2
});
在上面的示例中,MyClass
类型的项通过TransformBlock
传递。每个项目的属性Property1
和Property2
使用每个属性的单独任务
并行计算。然后等待这两个任务,当两个任务都完成时,结果将分配给项目的属性。最后返回已处理的项目
使用这种方法唯一需要注意的是,并行度将是内部并行操作和块选项的乘积。因此,在上面的例子中,平行度为2x2=4。准确地说,这将是最大并行度,因为两个内部计算中的一个可能比另一个慢。因此,在任何给定的时刻,实际的并行度都可能在2到4之间。“有没有一种方法可以让任务从TransformBlocks返回,并等到所有TransformBlocks都有一个完成的任务可以传递,然后再接受
任务”
“我不知道为什么你认为这会有帮助,但TDF块不是这样工作的。您要么接受一个项目,要么不接受,您不能接受一个项目并在以后决定接受它。我将测试它并报告与连接块相关的性能数字。然而,我也对其他非TDF解决方案感兴趣。一些关于BatchBlock的反馈:你是对的,非贪婪模式下的BatchBlock(需要获取所有项目,而不仅仅是第一个可用项目)会显著降低速度。我决定在TDF之外解决这个问题。我仍然希望在前面的每个变换块中并行运行func,然后按常规连接结果,而不是通过TDF。但是谢谢你给batchBlock的指针。@svick-很好地重新调整了batchBlock的用途,以补偿JoinBlock有限的输出元组数量。