C# 第三方物流数据流:有限容量和等待完成
为了简单起见,我在下面复制了一个真实场景作为LINQPad脚本: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
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);