3

I want to implement the consumer/producer pattern using the BufferBlock<T>, in the context of an ASP.NET request, but I also need:

  1. to buffer the 'produced' messages to prevent the queue filling up the available memory (say 50 items in the buffer at a time)
  2. The producer will 'produce' continuously, and the thread it runs on will be shutdown shortly after it produces a bunch of messages. (as the ASP.NET request ends)
  3. The consumer will 'consume' continuously, as multiple producers (ASP.NET requests) produce a bunch of messages each, so we think having a separate thread doing the consuming is the way to go.

[The component I ultimately need intermediates logging calls within a long running ASP.NET hosted REST service, where each request 'produces' many logging messages as it executes, and the consumer eventually 'consumes' them by writing them to some persistence store (which is comparatively slower, over the network).]

We've been following Steven Cleary's guidance on using the BufferBlock<T> here, but it seems that all these examples call the BufferBlock<T>.Complete() method which ends the production of messages. (i.e. once Complete() is called, calling SendAsync() is ignored by the block.)

So how can we use the BufferBlock<T> in a 'continuous' mode where the consumer continuously consumes while the producer continuously produces? I think without calling Complete().

We are using BoundedCapacity to define the buffer:

var _queue = new BufferBlock<TMessage>(new DataflowBlockOptions
    {
        BoundedCapacity = 50
    });

and then later...to start the consumer:

Task.Run(async () =>
{
    while (true)
    {
        try
        {
            while (await _queue.OutputAvailableAsync())
            {
                var message = await _queue.ReceiveAsync();

                try
                {
                    _storage.Store(message)
                }
                catch (Exception)
                {
                    //Log and Ignore exception and continue
                }
            }
        }
        catch (Exception)
        {
            //Log and Ignore exception and continue
        }
    }
});

So, with the consumer running endlessly, we now want to continuously call SendAsync() (by each ASP.NET request thread) and have messages queue up, while the consumer (an a thread pool thread) continuously consumes them.

How do you use BufferBlock<T> or other TPL types to achieve this?

UPDATE (22/7/2016): I ended up linking an ActionBlock to the BufferBlock and just passing the delegate _storage.Store(message) to the ActionBlock for easier maintenance, and to avoid the whole while loop and Complete() thing.

Jezz Santos
  • 355
  • 4
  • 15
  • 1
    I don't understand, what exactly is the problem with the solution you showed? – svick Jul 17 '16 at 23:24
  • There is no call to `Complete()` on the `BufferBlock` so wasn't confident that we are using `BufferBlock` correctly here, given that it has `BoundedCapacity`. Also don't think I understand precisely what happens when ASP.NET thread that calls `await SendAsync()` when the buffer is full, and then Response.End() is called, will the consumer ever see the produced message? So was looking for affirmation or otherwise that we are using it right/wrong for what we need. – Jezz Santos Jul 17 '16 at 23:40
  • 2
    There is no relation between `Complete()` and `BoundedCapacity`, `BoundedCapacity` works just fine without `Complete()`. And if you use `await SendAsync()` on a full buffer, execution of that method is not going to continue until the item can get into the block, so presumably, `Response.End()` is not going to be called. Or is that called by some other code? – svick Jul 17 '16 at 23:57
  • Thx @svick, that affirmation is what I needed. Response.End() is going to be called by the thread that called SendAsync(), so I think you are right there too. – Jezz Santos Jul 18 '16 at 01:30
  • @JezzSantos: The one danger of having an independent dataflow mesh is that any buffered messages would be lost when the app recycles. – Stephen Cleary Jul 19 '16 at 20:21
  • Thx @StephenCleary, good to keep in mind. Of course its always a risk and tradeoff. Our primary use case here is buffering logging calls, because the logging sink (serilog sink) is relatively slow (compared to the calling code), so we don't want the ASP.NET web request thread that is making the logging call waiting around for the logging sink to finish writing to it I/O. – Jezz Santos Jul 21 '16 at 23:53
  • @StephenCleary Stephen, would you be interested in joining a conversation we are having over at https://github.com/jezzsantos/Serilog.Sinks.Async/issues/5. We are trying to use the BufferBlock to solve a generic problem in a logging framework where ideally, a calling thread should not be blocked making a logging call, and having another thread writing the logging data to whatever sink is configured. I am not sure what you appetite for helping us over there, but it contextualises this SO post. Would you be interested in helping us solve that kind of issue with what you know about async? – Jezz Santos Jul 26 '16 at 01:52
  • I am in exactly the same situation as the OP. Any ideas are appreciated. – squashed.bugaboo Aug 23 '16 at 20:47

0 Answers0