How to remove a single, specific object from a ConcurrentBag<>?

C#C# 4.0

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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.

Solution 2 - C#

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.

Solution 3 - C#

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.

Solution 4 - C#

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);
}

Solution 5 - C#

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.

Solution 6 - C#

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);
    }
      
}

Solution 7 - C#

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);
        }
    }
}

Solution 8 - C#

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;
}

Solution 9 - C#

how about:

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

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBrandonView Question on Stackoverflow
Solution 1 - C#Mark ByersView Answer on Stackoverflow
Solution 2 - C#Hans PassantView Answer on Stackoverflow
Solution 3 - C#LarryView Answer on Stackoverflow
Solution 4 - C#RkaufmanView Answer on Stackoverflow
Solution 5 - C#Mikael SvensonView Answer on Stackoverflow
Solution 6 - C#hudaView Answer on Stackoverflow
Solution 7 - C#hudaView Answer on Stackoverflow
Solution 8 - C#Evgeniy NikulovView Answer on Stackoverflow
Solution 9 - C#Simon GulliveView Answer on Stackoverflow