Benefits of using BufferBlock<T> in dataflow networks
Asked Answered
K

3

26

I was wondering if there are benefits associated with using a BufferBlock linked to one or many ActionBlocks, other than throttling (using BoundedCapacity), instead of just posting directly to ActionBlock(s) (as long as throttling is not required).

Kelda answered 8/10, 2012 at 11:52 Comment(0)
W
27

If all you want to do is to forward items from one block to several others, you don't need BufferBlock.

But there are certainly cases where it is useful. For example, if you have a complex dataflow network, you might want to build it from smaller sub-networks, each one created in its own method. And to do this, you need some way to represent a group of blocks. In the case you mentioned, returning that single BufferBlock (probably as ITargetBlock) from the method would be an easy solution.

Another example where BufferBlock would be useful is if you wanted to send items from several source blocks to several target blocks. If you used BufferBlock as an intermediary, you don't have to connect each source block to each target block.

I'm sure there are many other examples where you could use BufferBlock. Of course, if you don't see any reason to use it in your case, then don't.

Wes answered 8/10, 2012 at 12:30 Comment(2)
I do feel that using BufferBlocks is "cleaner" way of communicating between dataflow blocks, but is the overhead (if any) of using BufferBlocks worth it?Kelda
That's for you to decide. If you feel it makes your code cleaner, do it. It does have some overhead, but I think that shouldn't be noticeable, unless you really care about performance.Wes
N
25

To add to svick's answer, there is another benefit of bufferblocks. If you have a block with multiple output links and want to balance between them, you have to turn the output blocks to bounded capacity of 1 and add a bufferblock to handle the queueing.

This is what we are planning to do:

  • Some code block will post data to the BufferBlock using it’s Post(T t) method.
  • This BufferBlock is linked to 3 ActionBlock instances using the LinkTo t) method of BufferBlock.

Note, that BufferBlock does not handover copies of the input data to all the target blocks it is linked to. Instead it does so to one target block only.Here we are expecting that when one target is busy processing the request.It will be handed over to the other target.Now let’s refer to the code below:

static void Main(string[] args)
{
    BufferBlock<int> bb = new BufferBlock<int>();

    ActionBlock<int> a1 = new ActionBlock<int>(a =>
    {
        Thread.Sleep(100);
        Console.WriteLine("Action A1 executing with value {0}", a);
    });

    ActionBlock<int> a2 = new ActionBlock<int>(a =>
    {
        Thread.Sleep(50);
        Console.WriteLine("Action A2 executing with value {0}", a);
    });

    ActionBlock<int> a3 = new ActionBlock<int>(a =>
    {
        Thread.Sleep(50);
        Console.WriteLine("Action A3 executing with value {0}", a);
    });

    bb.LinkTo(a1);
    bb.LinkTo(a2);
    bb.LinkTo(a3);

    Task t = new Task(() =>
        {
            int i = 0;
            while (i < 10)
            {
                Thread.Sleep(50);
                i++;
                bb.Post(i);
            }
        }
    );

    t.Start();
    Console.Read();
}

When executed it produces the following output:

  • Action A1 executing with value 1
  • Action A1 executing with value 2
  • Action A1 executing with value 3
  • Action A1 executing with value 4
  • Action A1 executing with value 5
  • Action A1 executing with value 6
  • Action A1 executing with value 7
  • Action A1 executing with value 8
  • Action A1 executing with value 9
  • Action A1 executing with value 10

This shows that only one target is actually executing all the data even when it’s busy(due to the Thread.Sleep(100) added purposefully).Why?

This is because all the target blocks are by default greedy in nature and buffers the input even when they are not able to process the data. To change this behavior we have set the Bounded Capacity to 1 in the DataFlowBlockOptions while initializing the ActionBlock as shown below.

static void Main(string[] args)
{
    BufferBlock<int> bb = new BufferBlock<int>();
    ActionBlock<int> a1 = new ActionBlock<int>(a =>
        {
            Thread.Sleep(100);
            Console.WriteLine("Action A1 executing with value {0}", a);
        }
        , new ExecutionDataflowBlockOptions {BoundedCapacity = 1});
    ActionBlock<int> a2 = new ActionBlock<int>(a =>
        {
            Thread.Sleep(50);
            Console.WriteLine("Action A2 executing with value {0}", a);
        }
        , new ExecutionDataflowBlockOptions {BoundedCapacity = 1});
    ActionBlock<int> a3 = new ActionBlock<int>(a =>
        {
            Thread.Sleep(50);
            Console.WriteLine("Action A3 executing with value {0}", a);
        }
        , new ExecutionDataflowBlockOptions {BoundedCapacity = 1});

    bb.LinkTo(a1);
    bb.LinkTo(a2);
    bb.LinkTo(a3);

    Task t = new Task(() =>
    {
        int i = 0;
        while (i < 10)
        {
            Thread.Sleep(50);
            i++;
            bb.Post(i);
        }
    });

    t.Start();
    Console.Read();
}

The output of this program is:

  • Action A1 executing with value 1
  • Action A2 executing with value 3
  • Action A1 executing with value 2
  • Action A3 executing with value 6
  • Action A3 executing with value 7
  • Action A3 executing with value 8
  • Action A2 executing with value 5
  • Action A3 executing with value 9
  • Action A1 executing with value 4
  • Action A2 executing with value 10

This clearly a distribution of the data across three ActionBlock(s) as expected.

Narceine answered 3/12, 2013 at 21:26 Comment(3)
Couldn't get the second example to compile.Levo
Hi, The "greedy" behavior could have been reachable just setting the BoundedCapacity property to "1" in each one of the ActionBlock instead of setting all of these configuration settings.Hartfield
I updated this answer to remove the greedy property which is only for Grouping Dataflow Blocks. Ideally I'd like to create a separate example for the grouping case and using greedy = false, as that is also a good use case for bath blocks..Narceine
P
5

No, the second example won't compile for a number of reasons: It's only possible to set greedy=false for a "grouping" dataflow block - not for an execution block; and then it has to be set via GroupingDataflowBlockOptions - not DataflowBlockOptions; and then it is set as a property value "{ Greedy = false }" not a constructor parameter.

If you want to throttle the capacity of an action block, do it by setting the value of the BoundedCapacity property of DataflowBlockOptions (though as the OP stated, they're already aware of this option). Like this:

var a1 = new ActionBlock<int>(
            i => doSomeWork(i), 
            new ExecutionDataflowBlockOptions {BoundedCapacity = 1}
        );
Papacy answered 25/2, 2017 at 19:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.