137

With the new ConcurrentBag<T> in .NET 4, how do you remove a certain, specific object from it when only TryTake() and TryPeek() are available?

I'm thinking of using TryTake() and then just adding the resulting object back into the list if I don't want to remove it, but I feel like I might be missing something. Is this the correct way?

Brandon
  • 13,956
  • 16
  • 72
  • 114
  • 2
    This gets me every time. Finally realized that 'bag' means you don't get to look inside. Like in a game of Scrabble. – Simon_Weaver Jun 21 '23 at 21:35
  • I haven't found a case where ConcurrentBag is appropriate yet, and it's touted constantly as a replacement for List. Lots of misinformation and obscurity around the issue -- and lots of good information, too, but almost all of it misses the mark on using a List concurrently. – omJohn8372 Aug 14 '23 at 14:55

9 Answers9

120

The short answer: you can't do it in an easy way.

The ConcurrentBag keeps a thread local queue for each thread and it only looks at other threads' queues once its own queue becomes empty. If you remove an item and put it back then the next item you remove may be the same item again. There is no guarantee that repeatedly removing items and putting them back will allow you to iterate over the all the items.

Two alternatives for you:

  • Remove all items and remember them, until you find the one you want to remove, then put the others back afterwards. Note that if two threads try to do this simultaneously you will have problems.
  • Use a more suitable data structure such as ConcurrentDictionary.
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 15
    SynchronizedCollection might also be a suitable substitute. – ILIA BROUDNO Oct 07 '16 at 19:59
  • 3
    @ILIABROUDNO -- you should put that as answer! That is SO MUCH better than a kludgey ConcurrentDictionary when you don't need a Dictionary – Denis Jul 14 '17 at 17:25
  • 4
    FYI, SynchronizedCollection is not available in .NET Core. As of the date of this comment, the System.Collections.Concurrent types are the way current way to go for .NET Core based implementations. – Matthew Snyder Jun 13 '19 at 14:58
  • 2
    I'm not sure which version of .NET Core was being used, but I'm working on a project based on the .NET Core 2.1 SDK and SynchronizedCollection is available in the Collections.Generic namespace now. – Lucas Leblanc Sep 25 '19 at 18:28
  • Be aware that SynchronizedCollection says it implements the collection as a `List`. Thus removal will cost O(n). – Oliver Bock Apr 27 '22 at 00:14
18

You can't. Its a bag, it isn't ordered. When you put it back, you'll just get stuck in an endless loop.

You want a Set. You can emulate one with ConcurrentDictionary. Or a HashSet that you protect yourself with a lock.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
14

The ConcurrentBag is great to handle a List where you can add items and enumerate from many thread, then eventually throw it away as its name is suggesting :)

As Mark Byers told, you can re-build a new ConcurrentBag that does not contains the item you wish to remove, but you have to protect this against multiple threads hits using a lock. This is a one-liner:

myBag = new ConcurrentBag<Entry>(myBag.Except(new[] { removedEntry }));

This works, and match the spirit in which the ConcurrentBag has been designed for.

Community
  • 1
  • 1
Larry
  • 17,605
  • 9
  • 77
  • 106
  • 18
    I feel this answer is misleading. To be clear, this does NOT provide any thread safety in the desired Remove operation. And putting a lock around it kinda defeats the purpose of using a concurrent collection. – ILIA BROUDNO Oct 07 '16 at 19:53
  • 1
    I agree. Well, to clarify a bit, the ConcurrentBag is designed to be filled, enumerated and thrown away with its whole content when it's done. Any attempts -including mine- to remove an item will result in a dirty hack. At least I tried to provide a answer, although the best is to use a better concurrent collection class, like the ConcurrentDictionary. – Larry Oct 07 '16 at 20:01
5

Mark is correct in that the ConcurrentDictionary is will work in the way you are wanting. If you wish to still use a ConcurrentBag the following, not efficient mind you, will get you there.

var stringToMatch = "test";
var temp = new List<string>();
var x = new ConcurrentBag<string>();
for (int i = 0; i < 10; i++)
{
    x.Add(string.Format("adding{0}", i));
}
string y;
while (!x.IsEmpty)
{
    x.TryTake(out y);
    if(string.Equals(y, stringToMatch, StringComparison.CurrentCultureIgnoreCase))
    {
         break;
    }
    temp.Add(y);
}
foreach (var item in temp)
{
     x.Add(item);
}
Rkaufman
  • 84
  • 1
  • 3
4

As you mention, TryTake() is the only option. This is also the example on MSDN. Reflector shows no other hidden internal methods of interest either.

Mikael Svenson
  • 39,181
  • 7
  • 73
  • 79
-1
public static void Remove<T>(this ConcurrentBag<T> bag, T item)
{
    while (bag.Count > 0)
    {
        T result;
        bag.TryTake(out result);

        if (result.Equals(item))
        {
            break; 
        }

        bag.Add(result);
    }

}
robinCTS
  • 5,746
  • 14
  • 30
  • 37
huda
  • 7
  • 1
  • 2
    `ConcurrentBag` is an unordered collection, but your code expects that `bag.TryTake` and `bag.Add` work in a FIFO manner. Your code assumes that `bag` includes `item`, it loops until it finds `item` in `bag`. Code only answers are discouraged, you should explain your solution. – GDavid Feb 27 '20 at 14:55
  • In your example, there are situations where the collection will not have an item that is required by other threads. – nim Dec 22 '21 at 13:44
-4

This is my extension class which I am using in my projects. It can a remove single item from ConcurrentBag and can also remove list of items from bag

public static class ConcurrentBag
{
    static Object locker = new object();

    public static void Clear<T>(this ConcurrentBag<T> bag)
    {
        bag = new ConcurrentBag<T>();
    }


    public static void Remove<T>(this ConcurrentBag<T> bag, List<T> itemlist)
    {
        try
        {
            lock (locker)
            {
                List<T> removelist = bag.ToList();

                Parallel.ForEach(itemlist, currentitem => {
                    removelist.Remove(currentitem);
                });

                bag = new ConcurrentBag<T>();


                Parallel.ForEach(removelist, currentitem =>
                {
                    bag.Add(currentitem);
                });
            }

        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }

    public static void Remove<T>(this ConcurrentBag<T> bag, T removeitem)
    {
        try
        {
            lock (locker)
            {
                List<T> removelist = bag.ToList();
                removelist.Remove(removeitem);                

                bag = new ConcurrentBag<T>();

                Parallel.ForEach(removelist, currentitem =>
                {
                    bag.Add(currentitem);
                });
            }

        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
}
huda
  • 4,107
  • 2
  • 21
  • 24
  • It's hard to believe it can work because you are creating new ConcurrentBag in local variable, but I am not sure. any test? – shtse8 Aug 06 '18 at 22:27
  • 3
    No, it doesn't work. How should it work, you are creating a only a new reference. The old one is still pointing to the old object.... It would work if you work with "ref" – Bin4ry Sep 30 '18 at 19:12
-6
public static ConcurrentBag<String> RemoveItemFromConcurrentBag(ConcurrentBag<String> Array, String Item)
{
    var Temp=new ConcurrentBag<String>();
    Parallel.ForEach(Array, Line => 
    {
       if (Line != Item) Temp.Add(Line);
    });
    return Temp;
}
GrandMasterFlush
  • 6,269
  • 19
  • 81
  • 104
-15

how about:

bag.Where(x => x == item).Take(1);

It works, I'm not sure how efficiently...

tkanzakic
  • 5,499
  • 16
  • 34
  • 41