24

I have a thread adding items to a BlockingCollection .

On another thread I am using foreach (var item in myCollection.GetConsumingEnumerable())

If there is a problem I want to break out of my foreach and my method and clear whatever is left in the BlockingCollection however I can't find a way to do it.

Any ideas?

Jon
  • 38,814
  • 81
  • 233
  • 382

6 Answers6

21

Just take out all remaining items:

while (collection.TryTake(out _)){}
mythz
  • 141,670
  • 29
  • 246
  • 390
19

I'm using this extension method:

public static void Clear<T>(this BlockingCollection<T> blockingCollection)
{
    if (blockingCollection == null)
    {
        throw new ArgumentNullException("blockingCollection");
    }

    while (blockingCollection.Count > 0)
    {
        T item;
        blockingCollection.TryTake(out item);
    }
}

I'm wondering if there's a better, less hacky, solution.

Paolo Moretti
  • 54,162
  • 23
  • 101
  • 92
  • 6
    With the new `out var` syntax: `while (blockingCollection.TryTake(out var _)){}` – Kjellski Apr 18 '17 at 20:20
  • any particular reason why `TryTake` instead of just plain old `Take`? Is there any scenario where the try version would return false in this example? – Assimilater Jul 25 '17 at 17:58
  • 3
    @Assimilater There's a race condition between the `while` check and `TryTake`. Another thread, for example, could take the last element out and `Take` would raise an `InvalidOperationException`. – Paolo Moretti Jul 27 '17 at 10:06
  • @PaoloMoretti Ah, I see. In my use case I only had one consumer so I didn't think of that :) – Assimilater Jul 27 '17 at 16:53
  • 2
    Similar to Kjellski's comment, but without requiring the new syntax, this could be shortened to `T _; while (blockingCollection.TryTake(out _)){}` – Tim Sparkles Mar 29 '18 at 00:19
  • Similar to @Timbo's comment (if we're going for code golf), it can be shorten to just `while (blockingCollection.TryTake(out _));` – alexyorke Dec 13 '20 at 04:24
  • @alexyorke feel write to update the answer, if you like :-) That syntax was not supported in 2012 by the C# 4.0 compiler – Paolo Moretti Dec 16 '20 at 16:39
9

Possibly use the overload of GetConsumingEnumerable that takes a CancellationToken; then, if anything goes wrong from the producing side, it can cancel the consumer.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Can I call the CancellationToken.Cancel within the foreach of GetConsumingEnumerbale? – Jon Nov 03 '11 at 20:10
  • I can do it within the GetConsumingEnumerable but it throws a OperationCanceledException. A simple break will leave the foreach loop but the BlockingCollection will still have items in it – Jon Nov 03 '11 at 20:24
  • @Jon: I wasn't suggesting cancelling in the consumer, but in the *producer* - I thought that was where you were detecting the error. If the consumer has noticed the problem, can't it just consume everything until there's nothing left? Does it need to notify the producer to say that it doesn't want any more items? Do you even need the same blocking collection any more? Couldn't you just throw it away? – Jon Skeet Nov 03 '11 at 21:33
  • I decided upon using a break in the consumer and then when I start producing again I just create a new instance of the BlockingCollection – Jon Nov 03 '11 at 21:42
  • 3
    A note to the unwary, `GetConsumingEnumerable` will block once the collection is empty (waiting on more entries) unless you've previously called `CompleteAdding`. It's in the sample code block, but not mentioned in the method description. – Gus May 22 '14 at 19:43
7

This worked for me

while (bCollection.Count > 0)
{
    var obj = bCollection.Take();
    obj.Dispose();
}

Take() removes from the collection and you can call any clean up on your object and the loop condition does not invoke any blocking calls.

clicky
  • 865
  • 2
  • 14
  • 31
  • This answer turned up in the low quality review queue, presumably because you didn't explain the code. If you do explain it (in your answer), you are far more likely to get more upvotes—and the questioner actually learns something! – The Guy with The Hat Sep 10 '14 at 15:03
  • This is inefficient, because depending on the underlying collection you are iterating over each element to determine the count, just to remove the first element. Also, the Dispose is unrelated to the question. – ckuri Feb 28 '20 at 14:01
1
BlockingCollection<T> yourBlockingCollection = new BlockingCollection<T>();

I assumed you mean clear your blocking collection. Jon's answer is more appropriate to your actual question I think.

msarchet
  • 15,104
  • 2
  • 43
  • 66
  • I meant I wanted to exit the foreach loop and empty whatever is in the BlockingCollection – Jon Nov 03 '11 at 20:23
  • 2
    can you please explain to me what the difference between removing those last items, and just: `theBlockingCollectionUsedIntheForEachLoop = new BlockingCollection();` is??? – Popmedic Sep 08 '16 at 19:45
-7

For just clearing the collection you can do:

myBlockingCollection.TakeWhile<*MyObject*>(qItem => qItem != null);

or just

myBlockingCollection.TakeWhile<*MyObject*>(qItem => true);
Ivan Ferić
  • 4,725
  • 11
  • 37
  • 47
Paullie
  • 15
  • 2
  • 4
    The TakeWhile is a LINQ porting and does not remove from the collection. It just creates a copy – Edmondo Jul 09 '13 at 11:40