13

I'm looping through a List of elements with foreach, like this:

foreach (Type name in aList) {
   name.doSomething();
}

However, in an another thread I am calling something like

aList.Remove(Element);

During runtime, this causes an InvalidOperationException: Collection was modified; enumeration operation may not execute. What is the best way to handle this (I would perfer it to be rather simple even at the cost of performance)?

Thanks!

Henrik Karlsson
  • 5,559
  • 4
  • 25
  • 42

6 Answers6

17

What is the best way to handle this (I would perfer it to be rather simple even at the cost of performance)?

Fundamentally: don't try to modify a non-thread-safe collection from multiple threads without locking. The fact that you're iterating is mostly irrelevant here - it just helped you find it quicker. It would have been unsafe for two threads to both be calling Remove at the same time.

Either use a thread-safe collection such as ConcurrentBag or make sure that only one thread does anything with the collection at a time.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Didn't think about that, but now that you said that it makes sense. Thanks, I will probably make use of that later, but for just this case lock works fine. – Henrik Karlsson Apr 04 '12 at 19:38
13

Method #1:

The simplest and least efficient method is to create a critical section for the readers and writers.

// Writer
lock (aList)
{
  aList.Remove(item);
}

// Reader
lock (aList)
{
  foreach (T name in aList)
  {
    name.doSomething();
  }
}

Method #2:

This is similar to method #1, but instead of holding the lock for the entire duration of the foreach loop you would copy the collection first and then iterate over the copy.

// Writer
lock (aList)
{
  aList.Remove(item);
}

// Reader
List<T> copy;
lock (aList)
{
  copy = new List<T>(aList);
}
foreach (T name in copy)
{
  name.doSomething();
}

Method #3:

It all depends on your specific situation, but the way I normally deal with this is to keep the master reference to the collection immutable. That way you never have to synchronize access on the reader side. The writer side of things needs a lock. The reader side needs nothing which means the readers stay highly concurrent. The only thing you need to do is mark the aList reference as volatile.

// Variable declaration
object lockref = new object();
volatile List<T> aList = new List<T>();

// Writer
lock (lockref)
{
  var copy = new List<T>(aList);
  copy.Remove(item);
  aList = copy;
}

// Reader
List<T> local = aList;
foreach (T name in local)
{
  name.doSomething();
}
Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
  • I like the variety of solutions here, but it should be noted in the first two examples that `aList` should be an internal object of the class otherwise a deadlock could occur. – Derek W May 07 '15 at 01:31
  • 1
    @DerekW: Yes, that is a good practice and well noted. In reality it is unlikely to actually cause a deadlock because the typical scenarios for such occurrences (nested locks, etc.) are not in play here. So unless another piece of code totally abuses `aList` the worst that would likely happen is increased lock contention. I think the whole `lock(this)` debate is a bit overblown and leans a little too far towards the paranoia side than some will admit. Again, it is still good practice to avoid such idioms though. – Brian Gideon May 07 '15 at 02:53
  • I should also point out while I'm thinking about it that #3 needs to be followed *exactly* as presented. Any deviation (like deciding that the assignment to `local` can be ommitted) will likely lead to random and spectacular failures. It might even rip a whole in spacetime for all I know. – Brian Gideon May 07 '15 at 02:55
  • I like solution #3 but just don't see how volatile helps here. As far as I see it does nothing and we can skip it without any harm – Shpand Jul 23 '20 at 14:59
  • @Shpand: The read of aList is happening outside the lock so to prevent the compiler from caching it and to make sure you get the latest copy you must use volatile. – Brian Gideon Jul 23 '20 at 21:44
  • @Brian Gideon you're not right. In case when Reader method looks smth like this volatile does absolutely nothing public void DoSomething() { List local = aList; foreach (T name in local) { name.doSomething(); } } – Shpand Jul 24 '20 at 10:55
  • @Shpand: Well actually....even in that trivial case you can't be sure. What if DoSomething is called in a loop and the compiler inlines it? And just because you may not be able to demonstrate a need for it on your current .NET platform does not mean that it won't be a problem in another. The .NET memory model is notorious for behaving differently on different platforms. I have other SO answers discussing this. Regardless my intent is provide a pattern that is safe even in the non-trivial general cases as well. – Brian Gideon Jul 24 '20 at 14:37
11

Thread A:

lock (aList) {
  foreach (Type name in aList) {
     name.doSomething();
  }
}

Thread B:

lock (aList) {
  aList.Remove(Element);
}

This ofcourse is really bad for performance.

Eugen Rieck
  • 64,175
  • 10
  • 70
  • 92
1

If you have more than one reader, then try a Reader-Writer Lock (.Net 3.5+), Slim: http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx

If you just have one reader, the just a lock on the list itself or a private object (but don't lock on the type itself), as shown in Eugen Rieck's answer.

user1198042
  • 61
  • 1
  • 4
0

if you just want to avoid the Exception use

foreach (Type name in aList.ToArray()) 
{ name.doSomething(); }

be aware the doSomething() is executed also in the case the element was removed in the other thread

NoMe
  • 11
  • 1
    Unfortunately `ToArray` and `Remove` could race possibly resulting in a different kind of exception. It is a good thought though! Refer to my [answer](http://stackoverflow.com/a/10018399/158779) to see how to get this to work correctly. – Brian Gideon Apr 04 '12 at 20:09
0

I can't specifically tell from your question, but (it looks like) you are performing an action on each item and then removing it. You might want to look in to BlockingCollection<T>, which has a method calling GetConsumingEnumerable() to see if it's a good fit for you. Here's a small sample.

void SomeMethod()
{
    BlockingCollection<int> col = new BlockingCollection<int>();

    Task.StartNew( () => { 

        for (int j = 0; j < 50; j++)
        {
            col.Add(j);
        }

        col.CompleteAdding(); 

     });

    foreach (var item in col.GetConsumingEnumerable())
    {
       //item is removed from the collection here, do something
       Console.WriteLine(item);
    }
}
Bryan Crosby
  • 6,486
  • 3
  • 36
  • 55