TPL Dataflows LinkTo多个消费者无法正常工作

时间:2012-09-20 08:43:52

标签: .net task-parallel-library tpl-dataflow

我有一个BufferBlock,我发布消息:

public class DelimitedFileBlock : ISourceBlock<string>
{
    private ISourceBlock<string> _source;
    _source = new BufferBlock<string>(new DataflowBlockOptions() { BoundedCapacity = 10000 });

    //Read a file
    While(!eof)
        row = read one row 
    //if consumers are slow, then sleep for a while
    while(!(_source as BufferBlock<string>).Post<string>(row))
    {
        Thread.Sleep(5000);
    }
}

这是一个包含2400万行的5GB文件。

我现在有一个使用ActionBlock的目标块:

public class SolaceTargetBlock : ITargetBlock<string>
       private ActionBlock<IBasicDataContract> _publishToSolaceBlock;

       public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, string messageValue, ISourceBlock<string> source, bool consumeToAccept)
    {
        //post to another block to publish
        bool success = _publishToSolaceBlock.Post(messageValue);

现在在控制台应用程序中,我指定:

 SolaceTargetBlock solaceTargetBlock1 = new SolaceTargetBlock("someparam", 
            new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 10, BoundedCapacity = 1 });
 SolaceTargetBlock solaceTargetBlock2 = new SolaceTargetBlock("someparam", 
            new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 10, BoundedCapacity = 1 });
 SolaceTargetBlock solaceTargetBlock3 = new SolaceTargetBlock("someparam", 
            new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 10, BoundedCapacity = 1 });

 DelimitedFileBlock delimitedFileBlock = new DelimitedFileBlock(csvFileInfo);

我将有限容量保持为1仅用于测试。

现在我使用LinkTo将这三个消费者链接到我的源:

 delimitedFileBlock.LinkTo(solaceTargetBlock1);      
 delimitedFileBlock.LinkTo(solaceTargetBlock2);      
 delimitedFileBlock.LinkTo(solaceTargetBlock3);      

这是10003行之后的Thread.Sleep(5000)语句,而while循环中的Post总是返回false。

我期待着因为我有LinkTo,所以solaceTargetBlocks在完成时将能够选择下一条消息,但是LinkTo没有清除BufferBlock。那么,我如何在多个消费者之间进行负载平衡。我是否必须接收并编写一个简单的负载平衡逻辑以在消费者之间分配?

1 个答案:

答案 0 :(得分:11)

来自Post method(强调我的)DataflowBlock<T> class的文档:

  

一旦目标区块决定接受或拒绝该项目,此方法将返回

这意味着目标可以选择拒绝阻止(这是您正在看到的行为)。

接下来,它说:

  

对于支持推迟提供的消息的目标块,或者可能在其Post实现中执行更多处理的块,请考虑使用SendAsync,它将立即返回并使目标能够推迟发布的消息,然后在SendAsync返回后使用它

这意味着您可能有更好的结果(取决于目标区块),因为您的邮件可能会被推迟,但仍会被处理,而不是完全拒绝。

我认为BoundedCapacity property和三个BufferBlock<T>实例上的ActionBlock<TInput>设置与您所看到的内容有关:

  • BufferBlock<T>上的最大缓冲区为10000;一旦你把10,000个项目放入队列,它将拒绝其余的(见上面的第二个引用),因为它无法处理它们(SendAsync也不能在这里工作,因为它无法缓冲消息到被推迟)。

  • ActionBlock<TInput>个实例的最大缓冲区为1,您有三个。

10,000 +(1 * 3)= 10,000 + 3 = 10,003

要解决这个问题,你需要做一些事情。

首先,您需要在创建ActionBlock<TInput>个实例时为MaxDegreeOfParallelism property ExecutionDataFlowBlockOptions设置更合理的值。

默认情况下,MaxDegreeOfParallelism的{​​{1}}设置为1;这保证了呼叫将被序列化,您不必担心线程安全。如果您希望ActionBlock<TInput>关注线程安全性,请保留此设置。

如果ActionBlock<T> 是线程安全的,那么您没有理由限制它,您应该将ActionBlock<TInput>设置为DataflowBlockOptions.Unbounded

如果你正在访问MaxDegreeOfParallelism中可以在有限的基础上同时访问的某种共享资源,那么你可能可能做错了。< / p>

如果您有某种共享资源,那么您可能应该通过另一个块运行它并在上设置ActionBlock<TInput>

其次,如果您关注吞吐量并且删除了项目,那么您应该设置MaxDegreeOfParallelism属性。

另请注意,您指出“如果消费者很慢,请暂时睡一觉”;如果正确连接块,没有理由这样做,你应该让数据流过,并将限制只放在你需要的地方。您的生产者不应负责限制消费者,让消费者负责限制。

最后,您的代码看起来不像您需要自己实现数据流块接口。你可以像这样构建它:

BoundedCapacity

另请注意,有三个// The source, your read lines will be posted here. var delimitedFileBlock = new BufferBlock<string>(); // The Action for the action blocks. Action<string> action = s => { /* Do something with the string here. */ }; // Create the action blocks, assuming that // action is thread-safe, no need to have it process one at a time // or to bound the capacity. var solaceActionBlock1 = new ActionBlock<string>(action, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, }); var solaceActionBlock2 = new ActionBlock<string>(action, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, }); var solaceActionBlock3 = new ActionBlock<string>(action, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, }); // Link everything. delimitedFileBlock.LinkTo(solaceTargetBlock1); delimitedFileBlock.LinkTo(solaceTargetBlock2); delimitedFileBlock.LinkTo(solaceTargetBlock3); // Now read the file, and post to the BufferBlock<T>: // Note: This is pseudo-code. while (!eof) { // Read the row. string row = ...; delimitedFileBlock.Post(read); } 个实例是不必要的,除非您需要将输出过滤到不同的操作(这里你没有这样做),所以上面的内容确实减少了(假设你的行为是线程) -safe,所以无论如何你都要将ActionBlock<TInput>增加到MaxDegreeOfParallelism

Unbounded