1

I have a buffer block that links to multiple action blocks:

bufferBlock.LinkTo(consumerActionBlock1, linkOptions);
bufferBlock.LinkTo(consumerActionBlock2, linkOptions);
bufferBlock.LinkTo(consumerActionBlockN, linkOptions);

The effect is that the buffer block accumulates items and once a worker-consumer can accept more items, it gets them (consumer is set to one item at a time). Like this, I have N worker threads whereas they all work on the same queue. So far so good.

Now I have a pipeline where there are two buffer blocks, each of them has workitems of certain priority:

bufferBlockPrioHigh.LinkTo(consumerActionBlockHigh1, linkOptions);
bufferBlockPrioHigh.LinkTo(consumerActionBlockHigh2, linkOptions);
bufferBlockPrioHigh.LinkTo(consumerActionBlockHighN, linkOptions);

bufferBlockPrioLow.LinkTo(consumerActionBlockLow1, linkOptions);
bufferBlockPrioLow.LinkTo(consumerActionBlockLow2, linkOptions);
bufferBlockPrioLow.LinkTo(consumerActionBlockLowN, linkOptions);

Also good. Now I want to achieve the behavior, that if there is nothing in the low priority queue, then the high priority queue could use its workers. I added this code:

bufferBlockPrioHigh.LinkTo(consumerActionBlockLow1, linkOptions, c => bufferBlockPrioLow.Count == 0);
bufferBlockPrioHigh.LinkTo(consumerActionBlockLow2, linkOptions, c => bufferBlockPrioLow.Count == 0);
bufferBlockPrioHigh.LinkTo(consumerActionBlockLowN, linkOptions, c => bufferBlockPrioLow.Count == 0);

The idea is that high priority buffer will try to send items to its own action blocks first. If they are all busy, it will try to send them to the action blocks of the low priority buffer, given that the low priority buffer is empty at the moment.

This seems to work. But what will happen, if all LinkTo fail? I.e. no own workers are available, and all conditional LinkTos will also return false (i.e. low priority queue is itself full)? Will the whole task now fail because of the new conditional LinkTos, or the task will eventually flow further once an action block, conditional or not, is free?

In other words, I'm seeking understanding in when multiple LinkTos are evaluated and how many times.

Maxim Zabolotskikh
  • 3,091
  • 20
  • 21
  • What's the reason for having a series of `consumerActionBlock1`, `consumerActionBlock2` ... `consumerActionBlockN`, instead of having a single `consumerActionBlock` configured with `MaxDegreeOfParallelism = N`? – Theodor Zoulias Jan 25 '22 at 19:42
  • Which blocks, if any, are configured with a limited `BoundedCapacity`? – Theodor Zoulias Jan 25 '22 at 19:44
  • 1
    All conusmer action blocks have a bounded capacity of 1. The reason was to control how many workers exactly are there (and reason behind this: resources. The system gets slow under heavy load). Now that you say it, it would be probably the same just to set MaxDegreeOfParallelism – Maxim Zabolotskikh Jan 26 '22 at 07:56
  • I found a question that might be relevant: [TPL DataFlow, link blocks with priority?](https://stackoverflow.com/questions/20974228/tpl-dataflow-link-blocks-with-priority) – Theodor Zoulias Jan 26 '22 at 08:42

1 Answers1

1

Well, in case somebody will find this question, my findings:

This code

bufferBlockPrioHigh.LinkTo(consumerActionBlockHigh1, linkOptions);
bufferBlockPrioHigh.LinkTo(consumerActionBlockLow1, linkOptions, c => bufferBlockPrioLow.Count == 0);

makes no sense, because the tasks will always flow into consumerActionBlockHigh1, even if the block is full. I ended up with this setup to achieve what I wanted:

bufferBlockPrioHigh.LinkTo(consumerActionBlockHigh1, linkOptions, bufferBlockPrioHigh.Count < capacity);
bufferBlockPrioHigh.LinkTo(consumerActionBlockLow1, linkOptions, c => bufferBlockPrioLow.Count == 0);
bufferBlockPrioHigh.LinkTo(consumerActionBlockHigh1, linkOptions);

Which means:

  • if bufferBlockPrioHigh is not yet full, use high prio worker at once
  • try to use other capacities: else if bufferBlockPrioLow is empty, use its worker
  • else use bufferBlockPrioHigh worker
Maxim Zabolotskikh
  • 3,091
  • 20
  • 21
  • This can fail easily. Nothing guarantees that the buffer blocks will have the same count from one call to the next. The predicate in `LinkTo` is meant to filter messages, not for this. Nothing forces you to use a *single* pipeline for all messages, or make direct connections. You could use a builder function to create blocks using different parameters, or entire pipeline, eg a high-priority one with one set of DOP, bounds, and a low priority one with different settings – Panagiotis Kanavos Jan 28 '22 at 15:23
  • But I want that Count() has a different value each time: only when count is high (i.e. queue full), shall I forward to a low prio queue. Or did I miss your point? Also I would like to have several pipelines, but my problem is that the system can handle like 10 workers in parallel (all in all). If I had a high prio queue with 10 and low prio queue with 10, the system would be slow. If I had both with 5, and high prio queue would be full and low prio empty, the high prio shall have only 5 workers to its disposal, whereas it could have had 10. – Maxim Zabolotskikh Jan 31 '22 at 13:20
  • In other words I want a pipeline, where there are N workers. Each priority queue has at least M workers to its disposal (M*numberOfPrios = N), but if one queue is empty and the other one is full than the full one would scale up and use all available N workers. – Maxim Zabolotskikh Jan 31 '22 at 13:24
  • [Capacity includes the currently processed items](https://stackoverflow.com/questions/26594604/does-boundedcapacity-include-items-currently-being-processed-in-tpl-dataflow) (I didn't know that). If you have blocks with DOP=Capacity==M*Priority, you may be able to use simple `LinkTo` to ensure that messages published from a client will go to the first available pipeline. `LinkTo` connections are evaluated in order – Panagiotis Kanavos Jan 31 '22 at 13:41
  • That is probably exactly the point that I don't understand. Let's say I have 1000 items in the high prio buffer. With the links from my answer my understanding is that it will fill the consumerActionBlockHigh1 till its capacity is reached, then do the same with consumerActionBlockLow1 (given that its buffer is empty) and then the 3d LinkTo to consumerActionBlockHigh1 will kick in - it will be evaluated, the message shall be linked, but will stay in the buffer till there is place for it in the ActionBlock. – Maxim Zabolotskikh Jan 31 '22 at 14:17
  • If I just LinkTo 2 ActionBlocks in order without conditions, it will always go to the first one (even if it's full, it will just wait in the buffer till there is place in the ActionBlock for it) and never to the second. That's my understanding, which I'm unsure of, unfortunately. So linking with different BoundedCapacities won't help, as the first LinkTo without condition will always resolve to true and the second one will never be evaluated. – Maxim Zabolotskikh Jan 31 '22 at 14:18