C# 第三方物流数据流:有限容量和等待完成

C# 第三方物流数据流:有限容量和等待完成,c#,task-parallel-library,tpl-dataflow,C#,Task Parallel Library,Tpl Dataflow,为了简单起见,我在下面复制了一个真实场景作为LINQPad脚本: var total = 1 * 1000 * 1000; var cts = new CancellationTokenSource(); var threads = Environment.ProcessorCount; int capacity = 10; var edbOptions = new ExecutionDataflowBlockOptions{BoundedCapacity = capacity, Cancel

为了简单起见,我在下面复制了一个真实场景作为LINQPad脚本:

var total = 1 * 1000 * 1000;
var cts = new CancellationTokenSource();
var threads = Environment.ProcessorCount;
int capacity = 10;

var edbOptions = new ExecutionDataflowBlockOptions{BoundedCapacity = capacity, CancellationToken = cts.Token, MaxDegreeOfParallelism = threads};
var dbOptions = new DataflowBlockOptions {BoundedCapacity = capacity, CancellationToken = cts.Token};
var gdbOptions = new GroupingDataflowBlockOptions {BoundedCapacity = capacity, CancellationToken = cts.Token};
var dlOptions = new DataflowLinkOptions {PropagateCompletion = true};

var counter1 = 0;
var counter2 = 0;

var delay1 = 10;
var delay2 = 25;

var action1 = new Func<IEnumerable<string>, Task>(async x => {await Task.Delay(delay1); Interlocked.Increment(ref counter1);});
var action2 = new Func<IEnumerable<string>, Task>(async x => {await Task.Delay(delay2); Interlocked.Increment(ref counter2);});

var actionBlock1 = new ActionBlock<IEnumerable<string>>(action1, edbOptions);
var actionBlock2 = new ActionBlock<IEnumerable<string>>(action2, edbOptions);

var batchBlock1 = new BatchBlock<string>(5, gdbOptions);
var batchBlock2 = new BatchBlock<string>(5, gdbOptions);

batchBlock1.LinkTo(actionBlock1, dlOptions);
batchBlock2.LinkTo(actionBlock2, dlOptions);

var bufferBlock1 = new BufferBlock<string>(dbOptions); 
var bufferBlock2 = new BufferBlock<string>(dbOptions); 

bufferBlock1.LinkTo(batchBlock1, dlOptions);
bufferBlock2.LinkTo(batchBlock2, dlOptions);

var bcBlock = new BroadcastBlock<string>(x => x, dbOptions);

bcBlock.LinkTo(bufferBlock1, dlOptions);
bcBlock.LinkTo(bufferBlock2, dlOptions);

var mainBlock = new TransformBlock<int, string>(x => x.ToString(), edbOptions);
mainBlock.LinkTo(bcBlock, dlOptions);

mainBlock.Dump("Main Block");
bcBlock.Dump("Broadcast Block");
bufferBlock1.Dump("Buffer Block 1");
bufferBlock2.Dump("Buffer Block 2");
actionBlock1.Dump("Action Block 1");
actionBlock2.Dump("Action Block 2");

foreach(var i in Enumerable.Range(1, total))
  await mainBlock.SendAsync(i, cts.Token);

mainBlock.Complete();

await Task.WhenAll(actionBlock1.Completion, actionBlock2.Completion);

counter1.Dump("Counter 1");
counter2.Dump("Counter 2");
var总计=1*1000*1000;
var cts=新的CancellationTokenSource();
var threads=Environment.ProcessorCount;
国际通行能力=10;
var-edbOptions=newexecutiondataflowblockoptions{BoundedCapacity=capacity,CancellationToken=cts.Token,MaxDegreeOfParallelism=threads};
var dbOptions=newdataflowblockoptions{BoundedCapacity=capacity,CancellationToken=cts.Token};
var gdbOptions=new GroupingDataflowBlockOptions{BoundedCapacity=capacity,CancellationToken=cts.Token};
var dlOptions=newdataflowlinkoptions{PropagateCompletion=true};
var=1=0;
var=2=0;
var=1=10;
var=2=25;
var action1=newfunc(异步x=>{await Task.Delay(delay1);Interlocked.Increment(ref counter1);});
var action2=newfunc(异步x=>{await Task.Delay(delay2);Interlocked.Increment(ref counter2);});
var actionBlock1=新的ActionBlock(action1,edbOptions);
var actionBlock2=新的ActionBlock(action2,edbOptions);
var batchBlock1=新的BatchBlock(5,GDB选项);
var batchBlock2=新的BatchBlock(5,GDB选项);
batchBlock1.LinkTo(actionBlock1,dlOptions);
batchBlock2.LinkTo(actionBlock2,dlOptions);
var bufferBlock1=新的BufferBlock(dbOptions);
var bufferBlock2=新的BufferBlock(dbOptions);
bufferBlock1.LinkTo(batchBlock1,dlOptions);
bufferBlock2.LinkTo(batchBlock2,dlOptions);
var bcBlock=新广播块(x=>x,dbOptions);
bcBlock.LinkTo(bufferBlock1,dlOptions);
bcBlock.LinkTo(bufferBlock2,dlOptions);
var mainBlock=新的TransformBlock(x=>x.ToString(),edbOptions);
mainBlock.LinkTo(bcBlock,dlOptions);
主块。转储(“主块”);
bcBlock.Dump(“广播块”);
缓冲块1.转储(“缓冲块1”);
缓冲块2.转储(“缓冲块2”);
动作块1.转储(“动作块1”);
动作块2.转储(“动作块2”);
foreach(可枚举范围中的var i(1,总计))
等待mainBlock.SendAsync(i,cts.Token);
mainBlock.Complete();
等待任务完成(actionBlock1.Completion,actionBlock2.Completion);
计数器1.转储(“计数器1”);
计数器2.转储(“计数器2”);
我对此代码有两个问题:

  • 虽然我将所有适当块的
    BoundedCapacity
    限制为10个元素,但似乎我几乎可以一次推送所有1000000条消息。这是预期的行为吗
  • 尽管整个网络都配置为传播完成,但似乎在调用
    mainBlock.Complete()
    后,所有块几乎立即完成。我希望
    counter1
    counter2
    变量都等于
    total
    。有没有办法实现这种行为

  • 是的,这是预期的行为:

    提供一个缓冲区,用于一次最多存储一个元素,在消息到达时用下一个元素覆盖每个消息

    这意味着,如果将
    BroadcastBlock
    链接到具有
    BoundedCapacity
    的块,您将丢失消息

    要解决这个问题,您可以创建一个自定义块,其行为类似于
    BroadcastBlock
    ,但可以保证传递到所有目标。但这样做并不简单,因此您可能会对一个更简单的变体感到满意(最初来自:

    public static ITargetBlock<T> CreateGuaranteedBroadcastBlock<T>(
        IEnumerable<ITargetBlock<T>> targets, DataflowBlockOptions options)
    {
        var targetsList = targets.ToList();
    
        var block = new ActionBlock<T>(
            async item =>
            {
                foreach (var target in targetsList)
                {
                    await target.SendAsync(item);
                }
            }, new ExecutionDataflowBlockOptions
            {
                BoundedCapacity = options.BoundedCapacity,
                CancellationToken = options.CancellationToken
            });
    
        block.Completion.ContinueWith(task =>
        {
            foreach (var target in targetsList)
            {
                if (task.Exception != null)
                    target.Fault(task.Exception);
                else
                    target.Complete();
            }
        });
    
        return block;
    }
    
    var bcBlock = CreateGuaranteedBroadcastBlock(
        new[] { bufferBlock1, bufferBlock2 }, dbOptions);