0

I am trying to implement a producer/consumer pattern for a stream of data that I am reading off of a controller asynchronously. I would like to use the BlockingCollection<T> in order to do so, but want to make sure I get the desired results. My consumer would look something like this:

// make sure there is actually some data in the buffer
if (!this.buffer.IsCompleted)
{
    // attempt to read from the buffer
    data = this.buffer.Take();

    // construct the message object
    message = this.ConvertToMessageObject(data);
}

Does the IsCompleted property actually block? So that if another thread was going to access the buffer, I would like it to wait and make sure that the buffer is actually not "completed" before calling on the Take method.

In my application, the desired effect would be to allow me to avoid constructing a new message object when the buffer is in fact empty. So that's why I am checking IsCompleted before going and trying to Take.

Additionally... I understand that the Take method no longer blocks once IsAddingCompleted = true. So I wouldn't want a consumer to grab data from the Take method that isn't valid, which it would have no choice of doing (if the consumer didn't know about the completed status). I think am having a really hard time trying to explain what I am worried about here...

Snoop
  • 1,046
  • 1
  • 13
  • 33

1 Answers1

2

Does the IsCompleted property actually block?

No, it will return immediately.

So that if another thread was going to access the buffer, I would like it to wait and make sure that the buffer is actually not "completed" before calling on the Take method.

This does that. Of course, if the buffer isn't completed when you call IsCompleted it'll return false. It might be completed after it has returned that value, and there might not be any items in the queue at the time you call IsCompleted.

In my application, the desired effect would be to allow me to avoid constructing a new message object when the buffer is in fact empty.

Take will throw an exception if the collection is blocked and there are no items in it, so you don't need to worry about that. It won't block forever nor will it construct a new message without any actual data. You can catch the exception outside of the loop to continue on after you're done.

All that said, it's way easier to just let BlockingCollection handle the iteration for you, rather than trying to construct your own consuming iterator (even though it's not that hard to do it yourself). You can just write:

foreach(var data in buffer.GetConsumingEnumerable())
    //...

It will consume items in the sequence until the buffer is completed, and then break out of the loop when there aren't, and won't ever be, any items in the buffer.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • My concern is... Having one thread in a state between checking `IsCompleted` and calling `Take`... while the other thread is still blocking on the `Take` method. What happens there? I don't want to call on `Take` if the buffer is empty, that is what I am trying to achieve. – Snoop Aug 23 '17 at 14:14
  • @Snoopy I mention exactly what happens in my answer. – Servy Aug 23 '17 at 14:14
  • I know, you more or less say "and there might not be any items in the queue at the time you call `IsCompleted`". This is my exact concern, how does it get addressed by this question? I am confused. – Snoop Aug 23 '17 at 14:17
  • @Snoopy Keep reading, rather than stopping after the first half of the answer. I answer that exact question right in the next paragraph (and also the one after it). – Servy Aug 23 '17 at 14:18
  • Okay, just want to say thanks (for answering the main question). Also, I understand what you said about the exception sort of. But does the exception happen even when you're not already blocking? Like if I just straight-up called `Take` on an `IsCompleted` buffer... Would that throw an exception? If that were the case then all I would have to do is make sure my client code is smart enough to not have the consumer do that. Sort of? – Snoop Aug 23 '17 at 14:21
  • @Snoopy You can easily write the code to try that for yourself and see what happens, although the statement in the answer does cover that. Like I said in the answer, writing your own iterator is easy enough, but *you don't have to*, just let `BlockingCollection` give you a consuming iterator. – Servy Aug 23 '17 at 14:24
  • Thanks for that information about the consuming enumerable. For what I am doing here I want to take one item at a time and display it somewhere, so I don't really need that option but it does sound useful for some other applications. I went and tried that experiment and it did end up throwing the `InvalidOperationException`... Is there some way to only grab one item from the `ConsumingEnumerable`? – Snoop Aug 23 '17 at 14:31
  • @Snoopy Sure, the same way you'd take one item from *any* `IEnumerable`, although if you really do just want one item then you can use the `Take` method. It will work just fine, just as I told you, and as you've seen from your own testing. – Servy Aug 23 '17 at 14:32