Search code examples
c#concurrencytask-parallel-librarymessage-passingtpl-dataflow

TPL Dataflow, whats the functional difference between Post() and SendAsync()?


I am confused about the difference between sending items through Post() or SendAsync(). My understanding is that in all cases once an item reached the input buffer of a data block, control is returned to the calling context, correct? Then why would I ever need SendAsync? If my assumption is incorrect then I wonder, on the contrary, why anyone would ever use Post() if the whole idea of using data blocks is to establish a concurrent and async environment.

I understand of course the difference technically in that Post() returns a bool whereas SendAsync returns an awaitable Task of bool. But what implications does that have? When would the return of a bool (which I understand is a confirmation whether the item was placed in the queue of the data block or not) ever be delayed? I understand the general idea of the async/await concurrency framework but here it does not make a whole lot sense because other than a bool the results of whatever is done to the passed-in item is never returned to the caller but instead placed in an "out-queue" and either forwarded to linked data blocks or discarded.

And is there any performance difference between the two methods when sending items?


Solution

  • 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);
    }