14

I expected the following to produce output from both publishers, but it only produces output from the first one:

var broadcastBlock = new BroadcastBlock<int>(null);
var transformBlock = new TransformBlock<int, int>(i => i*10);
var publish1 = new ActionBlock<int>(i => Console.WriteLine("Publisher 1:" + i));
var publish2 = new ActionBlock<int>(i => Console.WriteLine("Publisher 2:" + i));

broadcastBlock.LinkTo(transformBlock, new DataflowLinkOptions() { PropagateCompletion = true });
transformBlock.LinkTo(publish1, new DataflowLinkOptions() { PropagateCompletion = true });
transformBlock.LinkTo(publish2, new DataflowLinkOptions() { PropagateCompletion = true });

foreach (var i in Enumerable.Range(0, 5))
{
    broadcastBlock.Post(i);
}
broadcastBlock.Complete();
Task.WhenAll(publish1.Completion, publish2.Completion).Wait();

I'm obviously missing something fundamental here, any ideas?

i3arnon
  • 113,022
  • 33
  • 324
  • 344
Amit G
  • 5,165
  • 4
  • 28
  • 29

2 Answers2

20

You are linking 2 ActionBlocks to a single TransformBlock. You should be linking the 2 ActionBlocks to the BrodcastBlock and link the BroadcastBlock to the TransformBlock.

What you have:

BroadCast => Transfrom => ActionBlock
                       => ActionBlock

What you need:

Transfrom => BroadCast => ActionBlock
                       => ActionBlock
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Thanks for the reply. So is the issue that a Transform block cannot be linked to multiple targets directly, and we must use the Broadcast block as an intermediary? I might have missed it but didn't see the documentation alluding to this. It does work when I introduce a broadcast block in between – Amit G Apr 25 '14 at 11:26
  • 4
    @AmitG Some blocks can be linked to multiple targets but they don't "copy" the item to all the targets. For every item they only post to a single target. BroadcastBlock offers the item to all targets until its item changes. – i3arnon Apr 25 '14 at 11:35
  • 4
    It is my understanding that a `BroadcastBlock` only offers the **latest** value to all target blocks. This means that if the `TransformBlock` in this example has new values, the `ActionBlocks` might not receive all values - especially if they take longer to consume than the `TransformBlock` produces. – urbanhusky Sep 21 '17 at 11:06
  • 1
    @urbanhusky It only offers the latest to be received, but it will deliver to every linked target block that can accept the message. [See documentation](https://msdn.microsoft.com/en-us/library/hh160447(v=vs.110).aspx). At the very bottom: "`BroadcastBlock` ensures that the current element is broadcast to any linked targets before allowing the element to be overwritten." – Marc L. Jan 24 '18 at 14:22
  • @MarcL. It still does not guarantee delivery to _all_ targets. That is okay if you only require one of the targets to process the message - but if you need delivery to all targets, e.g. a multiplexer block, then you'll have to write your own block. – urbanhusky Jan 24 '18 at 19:33
  • @urbanhusky Ouch. On my reading of the above documentation, it does seem to guarantee delivery to all accepting, linked target buffers (e.g., buffer isn't full). (Of course, that "guarantee" is constrained by the durability of RAM, runtime stability, etc. I usually regale consumers with the *Tommy Boy* quote of choice,) Do you have a counterexample? If so, probably you or I should open another question. – Marc L. Jan 24 '18 at 22:19
  • @MarcL. I read it like you did. A `WriteOnceBlock` only accepts one element ever, dropping all following messages it *receives*. It cannot be overwritten. A `BroadcastBlock`'s element *can* be overwritten, but it "ensures that the current element is broadcast to any linked targets before allowing the element to be overwritten." But if it's receiving messages faster than the targets process them, so that messages 3, 4, 5 are received before message 2 was delivered to anyone because all targets are still busy processing message 1, will message 2, 3, 4 be buffered or overwritten by 5? – LWChris Apr 30 '19 at 13:16
  • @LWChris, my experience is that they will be buffered. – Marc L. May 10 '19 at 02:29
  • 1
    *"You are linking 2 `ActionBlocks` to a single `TransformBlock`. You should be linking the 2 `ActionBlock`s to the `BrodcastBlock` and link the `BroadcastBlock` to the `TransformBlock`."* -- The [`LinkTo`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.dataflow.dataflowblock.linkto) method points from the `source` to the `target`, not vice versa. So I think that the wording of the answer is confusing. The drawing is correct though. – Theodor Zoulias Apr 06 '22 at 23:29
0

I can't comment above, but I thought my findings on BroadcastBlock would be useful to someone:

  • As long as you are broadcasting to blocks that do NOT have a 'DataflowBlockOptions.BoundedCapacity' set, then ALL messages will be delivered.
  • Make sure that your source block is the LAST one you link up. I discovered you can create a race condition if you link it first. The source and broadcasting blocks can process messages BEFORE the target blocks are linked. It would depend on the application, but in my trivial test, I would only see the last message in my target/action blocks.

Edit: noticing my second comment is not relevant to OP based on how they structured it, but something I was running into because my SourceBlock was being populated in a Task.

Greg H
  • 1
  • 2