Click here to Skip to main content
14,640,918 members
Articles » Languages » C# » Enumerations
Posted 4 Feb 2010

Tagged as


71 bookmarked

Thread-safe enumeration in C#

Rate this:
4.85 (32 votes)
Please Sign up or sign in to vote.
4.85 (32 votes)
4 Feb 2010Public Domain
A way to have thread-safe foreach statements without explicit locking.


The IEnumerable interface in C# is a really useful abstraction. Introduction of LINQ in .NET 3.0 made it even more versatile. However, in a multithreaded environment, using it is wrought with peril, as the underlying collection may be modified anytime, instantly destroying your foreach loop or LINQ-expression.

I am going to suggest a simple trick that will make creating thread-safe enumerators super-easy, by strategically using another great interface: IDisposable.

Problems with iteration

As MSDN correctly states, "enumerating through a collection is intrinsically not a thread-safe procedure". Even if you use a synchronized collection (or one of the concurrent collections in .NET 4.0), and all methods use a lock statement internally, iteration using foreach may still fail. Another thread can change the collection when the control is inside the foreach loop - and thus, the collection is not locked - and bam, InvalidOperationException!

Traditionally, this problem is solved by wrapping the loop in a lock statement like this:

    foreach(var item in collection){
        // do stuff

The problem with this approach is that the locking object is public, and anyone anywhere can lock on it. And, that is just inviting deadlocks.

Another way to have iteration in a thread-safe way is to simply make a copy of a collection:

foreach(var item in collection.Clone()){
    // do stuff

That assumes that the Clone() method is thread-safe. Even when it is, this pattern cannot boast high performance - we're actually iterating twice through the whole collection, to say nothing of allocating and then garbage-collecting memory for a clone.

Certainly, a much better way would be to write something like:

foreach(var item in collection.ThreadSafeEnumerator()){
    // do stuff

and have it be automatically thread-safe like in the first example. This is how to achieve this.

Creating a thread-safe enumerator

A great thing about foreach (and, by extension, LINQ) is that it correctly executes the Dispose() method of the enumerator. That is, foreach actually becomes a try-finally statement, where the enumerator is created, then iterated inside a try block, and disposed in finally. The IEnumerator<T> interface is actually inherited from IDisposable; its non-generic counterpart is not (because in .NET 1.0, foreach didn't work like that).

Taking advantage of this, we will create an enumerator that enters a lock in the constructor, and exits in Dispose(). This way, the collection will stay locked throughout the entire iteration, and no rogue thread will be able to change it.

public class SafeEnumerator<T>: IEnumerator<T>
    // this is the (thread-unsafe)
    // enumerator of the underlying collection
    private readonly IEnumerator<T> m_Inner;
    // this is the object we shall lock on. 
    private readonly object m_Lock;

    public SafeEnumerator(IEnumerator<T> inner, object @lock)
        m_Inner = inner;
        m_Lock = @lock;
        // entering lock in constructor

    #region Implementation of IDisposable

    public void Dispose()
        // .. and exiting lock on Dispose()
        // This will be called when foreach loop finishes


    #region Implementation of IEnumerator

    // we just delegate actual implementation
    // to the inner enumerator, that actually iterates
    // over some collection
    public bool MoveNext()
        return m_Inner.MoveNext();

    public void Reset()

    public T Current
        get { return m_Inner.Current; }

    object IEnumerator.Current
        get { return Current; }


This is a trivial enumerator that enters lock on creation and exits on disposal. To actually use it, we must create a collection that uses it. For example:

public class MyList<T>: IList<T>{
    // the (thread-unsafe) collection that actually stores everything
    private List<T> m_Inner;
    // this is the object we shall lock on. 
    private readonly object m_Lock=new object();
    IEnumerator<T> IEnumerable<T>.GetEnumerator()
        // instead of returning an usafe enumerator,
        // we wrap it into our thread-safe class
        return new SafeEnumerator<T>(m_Inner.GetEnumerator(), m_Lock);
    // To be actually thread-safe, our collection
    // must be locked on all other operations
    // For example, this is how Add() method should look
    public void Add(T item)
    // ... the rest of IList<T> implementation goes here

This example shows a thread-safe wrapper around List<T>. This wrapper is absolutely synchronized - no other thread can do anything with it when it is used in a foreach loop.

Other neat stuff

Writing a thread-safe wrapper around List<T> (or any other collection) is useful; but we can make it even better. Let's make use of extension methods! First of all, here's a wrapper around IEnumerable<T>:

public class SafeEnumerable<T> : IEnumerable<T>
    private readonly IEnumerable<T> m_Inner;
    private readonly object m_Lock;

    public SafeEnumerable(IEnumerable<T> inner, object @lock)
        m_Lock = @lock;
        m_Inner = inner;

    #region Implementation of IEnumerable

    public IEnumerator<T> GetEnumerator()
        return new SafeEnumerator<T>(m_Inner.GetEnumerator(), m_Lock);

    IEnumerator IEnumerable.GetEnumerator()
        return GetEnumerator();


Using this wrapper, we can write an extension for all enumerable collections:

public static class EnumerableExtension
    public static IEnumerable<T> AsLocked<T>(this IEnumerable<T> ie, object @lock)
        return new SafeEnumerable<T>(ie, @lock);

And now, we can lock any collection by simply using this method:

// in a class...
public class MyThreadSafeEnumerable<T>{
    // come collection of items..
    private IEnumerable<T> m_Items;
    private readonly object m_Lock=new object();
    // and thread-safe getter for them!
    public IEnumerable<T> Items{
            return m_Items.AsLocked(m_Lock);
// .. or simply in loop
foreach(var item in someList.AsLocked(someLock)){
    // ...

Neat, huh? Of course, that last example is in fact the same locking method from the beginning of the article. Still, it's arguably more readable. And, when the lock is made private, it's even more readable and less deadlock-prone.

Additional considerations

While it's all good when using foreach, using this enumerator by explicitly calling collection.GetEnumerator() is more dangerous than before. Forget to call Dispose() on it, and your collection is stuck in a lock forever. Implementing the finalization pattern on the enumerator might help with it, but really, the way to go is to never use GetEnumerator() unless absolutely necessary. And really, don't use it even then.

Also, it must be noted that even a private lock object doesn't guarantee deadlock-free code. As code inside a foreach loop may be arbitrary, one can still manage to deadlock. For example, like this:

// thread 1:
foreach(var item in SafeCollection){
    // do stuff
        // do other stuff

// thread 2:
    // do stuff
    SafeCollection.Add(foo); // <-Deadlock!

Here, the first thread locks the collection, then tries to enter the lock on SomeObject.. which is held by the second thread, and that waits on the collection's lock to add something to it. So, the lock is never released, and the threads hang up. Deadlocks are tricky like that. To remedy such a situation, you can add timeout to Monitor.Enter() in the constructor, and throw an exception if the timeout expired. Exception is still not a correct behaviour for a program, but it's arguably better than a deadlock - and certainly easier to debug!

Another possible upgrade to the thread-safe enumerator is using ReaderWriterLock (or even better, ReaderWriterLockSlim) in place of Monitor. As iterations do not change the collection, it makes sense to allow many concurrent iterations at once, and only block concurrent changes to the collection. This is what ReaderWriterLock is for!


This article, along with any associated source code and files, is licensed under A Public Domain dedication


About the Author

Alexey Drobyshevsky
Software Developer (Senior)
Russian Federation Russian Federation
No Biography provided

Comments and Discussions

QuestionDispose ? Pin
Berni27-May-20 20:06
MemberBerni27-May-20 20:06 
QuestionProblem with Insert(int index, T item) Pin
Dismember30-Oct-19 0:10
MemberDismember30-Oct-19 0:10 
QuestionI don't see how you resolved problem with concurrency Pin
aldema16-Apr-15 7:52
Memberaldema16-Apr-15 7:52 
BugNo thread safe constructor Pin
Jochen Axt14-Jul-13 23:23
MemberJochen Axt14-Jul-13 23:23 
GeneralMy vote of 5 Pin
Jochen Axt14-Jul-13 21:15
MemberJochen Axt14-Jul-13 21:15 
QuestionThis is really great. Pin
essence14-Feb-13 8:05
Memberessence14-Feb-13 8:05 
QuestionNice One! Pin
Aron Kovacs22-Nov-12 23:19
MemberAron Kovacs22-Nov-12 23:19 
Questioncan you give some exmples using this code? Pin
gil_adino8-Jul-12 21:04
Membergil_adino8-Jul-12 21:04 
AnswerRe: can you give some exmples using this code? Pin
Alexey Drobyshevsky8-Jul-12 21:32
MemberAlexey Drobyshevsky8-Jul-12 21:32 
GeneralRe: can you give some exmples using this code? Pin
gil_adino10-Jul-12 1:53
Membergil_adino10-Jul-12 1:53 
GeneralMy vote of 5 Pin
tonyf888829-Dec-11 6:18
Membertonyf888829-Dec-11 6:18 
QuestionVery helpful, I made a small change Pin
tonyf888829-Dec-11 5:54
Membertonyf888829-Dec-11 5:54 
GeneralRe: Very helpful, I made a small change Pin
Member 943881217-Mar-14 7:32
MemberMember 943881217-Mar-14 7:32 
GeneralBug in the code - fixable though Pin
ByteGuru4-Apr-14 20:37
MemberByteGuru4-Apr-14 20:37 
GeneralI must be missing something. Pin
jgauffin9-Feb-10 7:15
Memberjgauffin9-Feb-10 7:15 
GeneralRe: I must be missing something. Pin
Alexey Drobyshevsky9-Feb-10 7:36
MemberAlexey Drobyshevsky9-Feb-10 7:36 
GeneralRe: I must be missing something. Pin
jgauffin9-Feb-10 7:39
Memberjgauffin9-Feb-10 7:39 
GeneralYes, yes, yes! Pin
Solid State Programmer5-Feb-10 9:20
professionalSolid State Programmer5-Feb-10 9:20 
GeneralCloning may be better Pin
supercat95-Feb-10 5:45
Membersupercat95-Feb-10 5:45 
GeneralRe: Cloning may be better Pin
Solid State Programmer5-Feb-10 9:25
professionalSolid State Programmer5-Feb-10 9:25 
GeneralRe: Cloning may be better Pin
supercat95-Feb-10 9:58
Membersupercat95-Feb-10 9:58 
JokeNotes Pin
kosat4-Feb-10 10:15
Memberkosat4-Feb-10 10:15 
GeneralRe: Notes Pin
Alexey Drobyshevsky4-Feb-10 20:59
MemberAlexey Drobyshevsky4-Feb-10 20:59 
GeneralRe: Notes Pin
supercat95-Feb-10 5:31
Membersupercat95-Feb-10 5:31 
GeneralRe: Notes Pin
Alexey Drobyshevsky5-Feb-10 12:00
MemberAlexey Drobyshevsky5-Feb-10 12:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.