First, TPL Dataflow is based on the actor model, but most of the time, you're going to be using only a specific subset of it. For example, it's quite rare to create a new dataflow block after receiving a message or to decide what block to send a message to (that's what linking is commonly used for). Though if you want to do that, you can.
Second, yeah, what you're doing is quite clumsy and it wouldn't work in many cases (e.g. if some block is already linked to dispatcher
).
To do this, I would use the fact that the delegate that executes in the block can have internal state. For simple cases, you can use a captured variable in a lambda. For example, if you had a block that prepends a GUID to a string
:
var id = Guid.NewGuid().ToString("N");
var block = new TransformBlock<string, string>(
input =>
{
if (input == "BECOME")
{
id = Guid.NewGuid().ToString("N");
return string.Empty;
}
return string.Format("{0}: {1}", id, input);
});
For more complicated cases, you can create a class that stores its state in a field and create a delegate to an instance method of that class:
class IdPrepender
{
private string id = Guid.NewGuid().ToString("N");
public string Process(string input)
{
if (input == "BECOME")
{
id = Guid.NewGuid().ToString("N");
return string.Empty;
}
return string.Format("{0}: {1}", id, input);
}
}
…
var block = new TransformBlock<string, string>(new IdPrepender().Process);
In both cases, if the block can execute in parallel, you have to make sure that the code is thread-safe.
Also, I would not overload string
like this. In the second case, you can take advantage of the fact that TPL Dataflow is not a pure actor model and add a "become" method to the class (IdPrepender
).
Am I right that the logic when message shall be sent only in some cases (not always) shall be implemented as custom block?
You don't need a custom block for this. You can use TransformManyBlock
whose delegate always returns 0 or 1 items.