C# 使用Batchblock.Triggerbatch()在TPL数据流管道中进行数据传播

C# 使用Batchblock.Triggerbatch()在TPL数据流管道中进行数据传播,c#,pipeline,throttling,tpl-dataflow,C#,Pipeline,Throttling,Tpl Dataflow,在生产者-消费者场景中,我有多个消费者,每个消费者向外部硬件发送一个操作,这可能需要一些时间。我的管道看起来有点像这样: BatchBlock-->TransformBlock-->BufferBlock-->(多个)操作块 我已将动作块的边界容量指定给1。 理论上,我想触发Batchblock,仅当我的一个ActionBlock可用于操作时,才将一组项发送到Transformblock。在此之前,Batchblock应该只保留缓冲元素,而不将它们传递给Transformblock。我的批量大小

在生产者-消费者场景中,我有多个消费者,每个消费者向外部硬件发送一个操作,这可能需要一些时间。我的管道看起来有点像这样:

BatchBlock-->TransformBlock-->BufferBlock-->(多个)操作块

我已将动作块的边界容量指定给1。 理论上,我想触发Batchblock,仅当我的一个ActionBlock可用于操作时,才将一组项发送到Transformblock。在此之前,Batchblock应该只保留缓冲元素,而不将它们传递给Transformblock。我的批量大小是可变的。由于Batchsize是强制性的,我的BatchBlock批量大小确实有一个很高的上限,但是我真的不希望达到这个上限,我想根据完成上述任务的Actionblocks的可用性触发我的批量

我是在Triggerbatch()方法的帮助下实现的。我正在调用Batchblock.Triggerbatch()作为ActionBlock中的最后一个操作。然而,有趣的是,经过几天的正常工作之后,管道出现了故障。检查后,我发现有时batchblock的输入在ActionBlock完成工作之后出现。在这种情况下,ActionBlock确实在工作结束时调用了Triggerbatch,但是由于此时Batchblock根本没有输入,因此对Triggerbatch的调用是徒劳的。过了一段时间,当输入确实流入Batchblock时,就没有人可以调用TriggerBatch并重新启动管道了。我一直在寻找一些东西,在那里我可以检查Batchblock的inputbuffer中是否确实存在一些东西,但是没有这样的功能可用,我也找不到一种方法来检查TriggerBatch是否有效

有人能为我的问题提出一个可能的解决办法吗。不幸的是,使用计时器触发批次对我来说不是一个选项。除了管道的开始,节流应该仅由一个ActionBlock的可用性控制

示例代码如下所示:

    static BatchBlock<int> _groupReadTags;

    static void Main(string[] args)
    {
        _groupReadTags = new BatchBlock<int>(1000);

        var bufferOptions = new DataflowBlockOptions{BoundedCapacity = 2};
        BufferBlock<int> _frameBuffer = new BufferBlock<int>(bufferOptions);
        var consumerOptions = new ExecutionDataflowBlockOptions { BoundedCapacity = 1};
        int batchNo = 1;


        TransformBlock<int[], int> _workingBlock = new TransformBlock<int[], int>(list =>
        {

            Console.WriteLine("\n\nWorking on Batch Number {0}", batchNo);
            //_groupReadTags.TriggerBatch();
            int sum = 0;

            foreach (int item in list)
            {
                Console.WriteLine("Elements in batch {0} :: {1}", batchNo, item);
                sum += item;

            }
            batchNo++;
            return sum;

        });

            ActionBlock<int> _worker1 = new ActionBlock<int>(async x =>
            {
                Console.WriteLine("Number from ONE :{0}",x);
                await Task.Delay(500);

                    Console.WriteLine("BatchBlock Output Count : {0}", _groupReadTags.OutputCount);

                _groupReadTags.TriggerBatch();



        },consumerOptions);

        ActionBlock<int> _worker2 = new ActionBlock<int>(async x =>
        {
            Console.WriteLine("Number from TWO :{0}", x);
            await Task.Delay(2000);
            _groupReadTags.TriggerBatch();

        }, consumerOptions);

        _groupReadTags.LinkTo(_workingBlock);
        _workingBlock.LinkTo(_frameBuffer);
        _frameBuffer.LinkTo(_worker1);
        _frameBuffer.LinkTo(_worker2);

        _groupReadTags.Post(10);
        _groupReadTags.Post(20);
        _groupReadTags.TriggerBatch();

        Task postingTask = new Task(() => PostStuff());
        postingTask.Start();
        Console.ReadLine();

    }



    static void PostStuff()
    {


        for (int i = 0; i < 10; i++)
            {
                _groupReadTags.Post(i);
                Thread.Sleep(100);
            }

        Parallel.Invoke(
            () => _groupReadTags.Post(100),
            () => _groupReadTags.Post(200),
            () => _groupReadTags.Post(300),
            () => _groupReadTags.Post(400),
            () => _groupReadTags.Post(500),
            () => _groupReadTags.Post(600),
            () => _groupReadTags.Post(700),
            () => _groupReadTags.Post(800)
                       );
    }
static BatchBlock\u groupReadTags;
静态void Main(字符串[]参数)
{
_groupReadTags=新批块(1000);
var bufferOptions=newdataflowblockoptions{BoundedCapacity=2};
BufferBlock _frameBuffer=新的BufferBlock(bufferOptions);
var consumerOptions=newexecutiondataflowblockoptions{BoundedCapacity=1};
int batchNo=1;
TransformBlock\u workingBlock=新TransformBlock(列表=>
{
Console.WriteLine(“\n\n处理批号{0}”,批次号);
//_groupReadTags.TriggerBatch();
整数和=0;
foreach(列表中的int项)
{
WriteLine(“批处理{0}::{1}中的元素”,批处理号,项);
总和+=项目;
}
batchNo++;
回报金额;
});
ActionBlock\u worker1=新的ActionBlock(异步x=>
{
WriteLine(“从一开始的数字:{0}”,x);
等待任务。延迟(500);
WriteLine(“BatchBlock输出计数:{0}”,_groupReadTags.OutputCount);
_groupReadTags.TriggerBatch();
},消费量);
ActionBlock\u worker2=新的ActionBlock(异步x=>
{
WriteLine(“两个数:{0}”,x);
等待任务。延迟(2000);
_groupReadTags.TriggerBatch();
},消费量);
_groupReadTags.LinkTo(_工作块);
_workingBlock.LinkTo(_帧缓冲区);
_frameBuffer.LinkTo(_worker1);
_frameBuffer.LinkTo(_worker2);
_groupReadTags.Post(10);
_groupReadTags.Post(20);
_groupReadTags.TriggerBatch();
任务发布任务=新任务(()=>PostStuff());
postingTask.Start();
Console.ReadLine();
}
静态void PostStuff()
{
对于(int i=0;i<10;i++)
{
_groupReadTags.Post(i);
睡眠(100);
}
并行调用(
()=>_groupReadTags.Post(100),
()=>_groupReadTags.Post(200),
()=>_groupReadTags.Post(300),
()=>_groupReadTags.Post(400),
()=>_groupReadTags.Post(500),
()=>_groupReadTags.Post(600),
()=>_groupReadTags.Post(700),
()=>_groupReadTags.Post(800)
);
}

我发现以这种方式使用
TriggerBatch
是不可靠的:

    _groupReadTags.Post(10);
    _groupReadTags.Post(20);
    _groupReadTags.TriggerBatch();
显然,
TriggerBatch
旨在在块内使用,而不是像这样在块外使用。我已经看到这一结果导致了奇怪的计时问题,比如下一批中的项目被包括在当前批中,尽管TriggerBatch是首先调用的


请参阅我对这个问题的回答,了解使用
数据流块的替代方法。封装

我发现以这种方式使用
TriggerBatch
是不可靠的:

    _groupReadTags.Post(10);
    _groupReadTags.Post(20);
    _groupReadTags.TriggerBatch();
显然,
TriggerBatch
旨在在块内使用,而不是像这样在块外使用。我已经看到这一结果导致了奇怪的计时问题,比如下一批中的项目被包括在当前批中,尽管TriggerBatch是首先调用的

请参阅我对这个问题的回答,了解使用
DataflowBlock的替代方法。封装

这里是一个具有一些额外功能的替代实现。它包括一个带有此签名的
TriggerBatch
方法:

public int TriggerBatch(int nextMinBatchSizeIfEmpty);
如果输入队列不为空,调用此方法将立即触发批处理,否则它将设置一个仅影响下一批处理的临时
MinBatchSize
。您可以为
nextMinBatchSizeIfEmpty
调用此方法,以确保在批处理
public int TriggerBatch(Func<T[], bool> condition);