TPL Dataflow, whats the functional difference between Post() and SendAsync()?
The documentation makes this reasonably clear, IMO. In particular, for Post
:
This method will return once the target block has decided to accept or decline the item, but unless otherwise dictated by special semantics of the target block, it does not wait for the item to actually be processed.
And:
For target blocks that support postponing offered messages, or for blocks that may do more processing in their
Post
implementation, consider usingSendAsync
, which will return immediately and will enable the target to postpone the posted message and later consume it afterSendAsync
returns.
In other words, while both are asynchronous with respect to processing the message, SendAsync
allows the target block to decide whether or not to accept the message asynchronously too.
It sounds like SendAsync
is a generally "more asynchronous" approach, and one which is probably encouraged in general. What isn't clear to me is why both are required, as it certainly sounds like Post
is broadly equivalent to using SendAsync
and then just waiting on the result. As noted in comments, there is one significant difference: if the buffer is full, Post
will immediately reject, whereas SendAsync
doesn't.
To see the difference, you need a situation where blocks will postpone their messages. In this case, Post
will return false
immediately, whereas SendAsync
will return a Task
that will be completed when the block decides what to do with the message. The Task
will have a true
result if the message is accepted, and a false
result if not.
One example of a postponing situation is a non-greedy join. A simpler example is when you set BoundedCapacity
:
[TestMethod]
public void Post_WhenNotFull_ReturnsTrue()
{
var block = new BufferBlock<int>(new DataflowBlockOptions {BoundedCapacity = 1});
var result = block.Post(13);
Assert.IsTrue(result);
}
[TestMethod]
public void Post_WhenFull_ReturnsFalse()
{
var block = new BufferBlock<int>(new DataflowBlockOptions { BoundedCapacity = 1 });
block.Post(13);
var result = block.Post(13);
Assert.IsFalse(result);
}
[TestMethod]
public void SendAsync_WhenNotFull_ReturnsCompleteTask()
{
// This is an implementation detail; technically, SendAsync could return a task that would complete "quickly" instead of already being completed.
var block = new BufferBlock<int>(new DataflowBlockOptions { BoundedCapacity = 1 });
var result = block.SendAsync(13);
Assert.IsTrue(result.IsCompleted);
}
[TestMethod]
public void SendAsync_WhenFull_ReturnsIncompleteTask()
{
var block = new BufferBlock<int>(new DataflowBlockOptions { BoundedCapacity = 1 });
block.Post(13);
var result = block.SendAsync(13);
Assert.IsFalse(result.IsCompleted);
}
[TestMethod]
public async Task SendAsync_BecomesNotFull_CompletesTaskWithTrueResult()
{
var block = new BufferBlock<int>(new DataflowBlockOptions { BoundedCapacity = 1 });
block.Post(13);
var task = block.SendAsync(13);
block.Receive();
var result = await task;
Assert.IsTrue(result);
}
[TestMethod]
public async Task SendAsync_BecomesDecliningPermanently_CompletesTaskWithFalseResult()
{
var block = new BufferBlock<int>(new DataflowBlockOptions { BoundedCapacity = 1 });
block.Post(13);
var task = block.SendAsync(13);
block.Complete();
var result = await task;
Assert.IsFalse(result);
}