C# TPL数据流,仅在所有源数据块完成时保证完成
当两个转换块都完成时,如何重新编写代码?我认为completion意味着它被标记为complete,“out queue”是空的C# TPL数据流,仅在所有源数据块完成时保证完成,c#,concurrency,task-parallel-library,tpl-dataflow,C#,Concurrency,Task Parallel Library,Tpl Dataflow,当两个转换块都完成时,如何重新编写代码?我认为completion意味着它被标记为complete,“out queue”是空的 public Test() { broadCastBlock = new BroadcastBlock<int>(i => { return i; }); transformBlock1 = new TransformBlock&
public Test()
{
broadCastBlock = new BroadcastBlock<int>(i =>
{
return i;
});
transformBlock1 = new TransformBlock<int, string>(i =>
{
Console.WriteLine("1 input count: " + transformBlock1.InputCount);
Thread.Sleep(50);
return ("1_" + i);
});
transformBlock2 = new TransformBlock<int, string>(i =>
{
Console.WriteLine("2 input count: " + transformBlock1.InputCount);
Thread.Sleep(20);
return ("2_" + i);
});
processorBlock = new ActionBlock<string>(i =>
{
Console.WriteLine(i);
});
//Linking
broadCastBlock.LinkTo(transformBlock1, new DataflowLinkOptions { PropagateCompletion = true });
broadCastBlock.LinkTo(transformBlock2, new DataflowLinkOptions { PropagateCompletion = true });
transformBlock1.LinkTo(processorBlock, new DataflowLinkOptions { PropagateCompletion = true });
transformBlock2.LinkTo(processorBlock, new DataflowLinkOptions { PropagateCompletion = true });
}
public void Start()
{
const int numElements = 100;
for (int i = 1; i <= numElements; i++)
{
broadCastBlock.SendAsync(i);
}
//mark completion
broadCastBlock.Complete();
processorBlock.Completion.Wait();
Console.WriteLine("Finished");
Console.ReadLine();
}
}
公共测试()
{
broadCastBlock=新的broadCastBlock(i=>
{
返回i;
});
transformBlock1=新的TransformBlock(i=>
{
Console.WriteLine(“1输入计数:+transformBlock1.InputCount”);
睡眠(50);
返回(“1_”+i);
});
transformBlock2=新的TransformBlock(i=>
{
Console.WriteLine(“2输入计数:+transformBlock1.InputCount”);
睡眠(20);
返回(“2_”+i);
});
processorBlock=新动作块(i=>
{
控制台写入线(i);
});
//连接
broadCastBlock.LinkTo(transformBlock1,新的DataflowLinkOptions{PropagateCompletion=true});
broadCastBlock.LinkTo(transformBlock2,新的DataflowLinkOptions{PropagateCompletion=true});
transformBlock1.LinkTo(processorBlock,新数据流链接选项{PropagateCompletion=true});
transformBlock2.LinkTo(processorBlock,新的DataflowLinkOptions{PropagateCompletion=true});
}
公开作废开始()
{
常数整数=100;
对于(int i=1;i,这里的问题是,您正在设置每次调用链接块的时间,以及转换块中不同的等待时间
从(矿山重点)的文件中:
向IDataflowBlock发出信号,表示它不应接受,也不应再产生任何消息,也不应再使用任何延迟的消息
因为您在每个实例中错开了等待时间,transformBlock2
(等待20毫秒)在transformBlock1
(等待50毫秒)之前完成。transformBlock2
首先完成,然后将信号发送到processorBlock
,然后显示“我不接受任何其他内容”(而且,transformBlock1
尚未生成其所有消息)
请注意,在transformBlock1
之前处理transformBlock1
不是绝对可以保证的;线程池(假设您使用的是默认调度程序)将以不同的顺序处理任务是可行的(但很可能不会,因为一旦20毫秒的项目完成,它将从队列中窃取工作)
您的管道如下所示:
broadcastBlock
/ \
transformBlock1 transformBlock2
\ /
processorBlock
broadcastBlock
/ \
transformBlock1 transformBlock2
| |
processorBlock1 processorBlock2
为了解决这个问题,您需要一个如下所示的管道:
broadcastBlock
/ \
transformBlock1 transformBlock2
\ /
processorBlock
broadcastBlock
/ \
transformBlock1 transformBlock2
| |
processorBlock1 processorBlock2
只需创建两个单独的实例即可实现,如下所示:
// The action, can be a method, makes it easier to share.
Action<string> a = i => Console.WriteLine(i);
// Create the processor blocks.
processorBlock1 = new ActionBlock<string>(a);
processorBlock2 = new ActionBlock<string>(a);
// Linking
broadCastBlock.LinkTo(transformBlock1,
new DataflowLinkOptions { PropagateCompletion = true });
broadCastBlock.LinkTo(transformBlock2,
new DataflowLinkOptions { PropagateCompletion = true });
transformBlock1.LinkTo(processorBlock1,
new DataflowLinkOptions { PropagateCompletion = true });
transformBlock2.LinkTo(processorBlock2,
new DataflowLinkOptions { PropagateCompletion = true });
这里有一个非常重要的注意事项:在创建ActionBlock
时,默认设置是将传递给它的实例上的设置为1
这意味着传递给ActionBlock
的调用是线程安全的,一次只执行一个
因为现在有两个ActionBlock
实例指向同一个Action
委托,所以不能保证线程安全
如果您的方法是线程安全的,那么您不必做任何事情(这将允许您将MaxDegreeOfParallelism
属性设置为,因为没有理由阻止)
如果它不是线程安全的,并且您需要保证它,那么您需要求助于传统的同步原语,如
在这种情况下,您可以这样做(尽管它显然不需要,因为上的是线程安全的):
//锁。
var l=新对象();
//该操作可以是一种方法,使其更易于共享。
行动a=i=>{
//确保一次只打一个电话。
锁(l)控制台。写线(i);
};
//等等。。。
问题正是casperOne在回答中所说的。一旦第一个转换块完成,处理器块进入“完成模式”:它将处理其输入队列中的剩余项,但不会接受任何新项
不过,有一个比将处理器块一分为二更简单的修复方法:不要设置PropagateCompletion
,而是在两个转换块都完成时手动设置处理器块的完成:
Task.WhenAll(transformBlock1.Completion, transformBlock2.Completion)
.ContinueWith(_ => processorBlock.Complete());
svick回答的另一个问题是:为了与您使用PropagateCompletion选项获得的行为保持一致,您还需要在前一个块出现故障时转发异常。下面这样的扩展方法也可以解决这一问题:
public static void CompleteWhenAll(this IDataflowBlock target, params IDataflowBlock[] sources) {
if (target == null) return;
if (sources.Length == 0) { target.Complete(); return; }
Task.Factory.ContinueWhenAll(
sources.Select(b => b.Completion).ToArray(),
tasks => {
var exceptions = (from t in tasks where t.IsFaulted select t.Exception).ToList();
if (exceptions.Count != 0) {
target.Fault(new AggregateException(exceptions));
} else {
target.Complete();
}
}
);
}
其他答案很清楚,当一个块有两个以上的源时,为什么PropagateCompletion=true会把事情搞砸
为了提供一个简单的问题解决方案,您可能想看看一个开源库,它通过内置更智能的完成规则来解决此类问题。(它在内部使用TPL数据流链接,但支持复杂的完成传播。该实现看起来与Wheall类似,但也处理动态链接添加。请检查和了解impl详细信息。)
我稍微更改了您的代码,以便使用DataflowEx实现所有功能:
public CompletionDemo1()
{
broadCaster = new BroadcastBlock<int>(
i =>
{
return i;
}).ToDataflow();
transformBlock1 = new TransformBlock<int, string>(
i =>
{
Console.WriteLine("1 input count: " + transformBlock1.InputCount);
Thread.Sleep(50);
return ("1_" + i);
});
transformBlock2 = new TransformBlock<int, string>(
i =>
{
Console.WriteLine("2 input count: " + transformBlock2.InputCount);
Thread.Sleep(20);
return ("2_" + i);
});
processor = new ActionBlock<string>(
i =>
{
Console.WriteLine(i);
}).ToDataflow();
/** rather than TPL linking
broadCastBlock.LinkTo(transformBlock1, new DataflowLinkOptions { PropagateCompletion = true });
broadCastBlock.LinkTo(transformBlock2, new DataflowLinkOptions { PropagateCompletion = true });
transformBlock1.LinkTo(processorBlock, new DataflowLinkOptions { PropagateCompletion = true });
transformBlock2.LinkTo(processorBlock, new DataflowLinkOptions { PropagateCompletion = true });
**/
//Use DataflowEx linking
var transform1 = transformBlock1.ToDataflow();
var transform2 = transformBlock2.ToDataflow();
broadCaster.LinkTo(transform1);
broadCaster.LinkTo(transform2);
transform1.LinkTo(processor);
transform2.LinkTo(processor);
}
public CompletionDemo1()
{
广播者=新广播块(
i=>
{
返回i;
}).ToDataflow();
transformBlock1=新TransformBlock(
i=>
{
Console.WriteLine(“1输入计数:+transformBlock1.InputCount”);
睡眠(50);
返回(“1_”+i);
});
transformBlock2=新TransformBlock(
i=>
{
Console.WriteLine(“2输入计数:+transformBlock2.InputCount”);
睡眠(20);
返回(“2_”+i);
});
处理器=新动作块(
i=>
{
控制台写入线(i);
public static void PropagateCompletionOfAll(IDataflowBlock[] sources,
IDataflowBlock target)
{
_ = Task.WhenAll(sources.Select(b => b.Completion)).ContinueWith(t =>
{
if (t.IsFaulted)
{
target.Fault(t.Exception);
}
else
{
target.Complete();
}
}, TaskScheduler.Default);
}
PropagateCompletionOfAll(new[] { transformBlock1, transformBlock2 }, processorBlock);