-1

BlockingCollection is a wrapper around IProducerConsumerCollection. My understanding is that calling BlockingCollection.Take checks if there is an item available (using its own counter). Then it uses TryTake on its underlying collection to retrieve said item.

Now of course there has to actually exist an item in the underlying collection (and no external modification happening). But what happens if TryTake fails for some other reason, and the underlying collection returns false to TryTake? Does the wrapper just give up, or does it try to take the item again (supposedly succeeding next time)?

The ominous "other reason" could be an internal delay e.g. if the data structure is distributed.

mafu
  • 31,798
  • 42
  • 154
  • 247
  • PLEASE leave a comment what's wrong with the question. I honestly have no idea anymore. – mafu Mar 22 '20 at 23:22

2 Answers2

2

Well, the only thing you can definitely take for granted is what's written in the documentation. But you can just take a look at the source code:

public T Take()
{
    T item;

    if (!TryTake(out item, Timeout.Infinite, CancellationToken.None))
    {
        throw new InvalidOperationException(SR.GetString(SR.BlockingCollection_CantTakeWhenDone));
    }

    return item;
}

In other words, the reason why TryTake returns false is irrelevant; Take will throw.

Joey
  • 344,408
  • 85
  • 689
  • 683
  • However, the TryTake in this answer refers to BlockingCollection internally, not to the underlying instance. So right now, this is not a complete answer. – mafu Mar 29 '20 at 06:33
0

@Joey's answer linked to the source code and gave the right result, but the problem is a little more nuanced.

BC.Take calls BC.TryTake (not on the underlying collection). BC.TryTake then waits for a semaphore. This semaphore is incremented when an item was added to the BC.

Then it calls Underlying.TryTake and throws directly if that returns false. Abbreviated:

//If an item was successfully removed from the underlying collection.
removeSucceeded = m_collection.TryTake(out item);
if (!removeSucceeded) {
    // Check if the collection is empty which means that the collection was modified outside BlockingCollection
    throw new InvalidOperationException (SR.GetString(SR.BlockingCollection_Take_CollectionModified));
}

This is the only place where Underlying.TryTake is called in BC.

So TryTake is indeed never allowed to fail. However, it is guaranteed (by the current BC code) that TryTake is only called after a corresponding TryAdd. So the underlying data structure just needs to make sure that the operations of TryTake are never delayed beyond the end of operations of TryAdd, since BC will not attempt to TryTake while the TryAdd of the sole element is still in progress.

mafu
  • 31,798
  • 42
  • 154
  • 247