Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/321.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# TPL数据流,仅在所有源数据块完成时保证完成_C#_Concurrency_Task Parallel Library_Tpl Dataflow - Fatal编程技术网

C# TPL数据流,仅在所有源数据块完成时保证完成

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&

当两个转换块都完成时,如何重新编写代码?我认为completion意味着它被标记为complete,“out queue”是空的

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);