Thread-safe enumeration in C#

I had a problem with the project I’m working on at work where on the base class I had a list which its child classes need to have access to in a read-only capacity but manipulation to the list itself is handled by the base class only. However, the standard C# List<T> and Enumerator<T> are not thread-safe and we started seeing problems when the list is modified by one thread whilst another is trying to loop through it.

Whilst looking for a clean solution we found this article on CodeProject:

http://www.codeproject.com/KB/cs/safe_enumerable.aspx

The article had covered much of the implementation you need, but left some gaps you need to plug yourself for a thread-safe list which I have included below along with some of the useful methods you’d find on List<T>:

Thread-safe Enumerator<T>

/// <summary>
/// A thread-safe IEnumerator implementation.
/// See: http://www.codeproject.com/KB/cs/safe_enumerable.aspx
/// </summary>
public class SafeEnumerator<T>: IEnumerator<T>
{
    // this is the (thread-unsafe)
    // enumerator of the underlying collection
    private readonly IEnumerator<T> _inner;

    // this is the object we shall lock on.
    private readonly object _lock;

    public SafeEnumerator(IEnumerator<T> inner, object @lock)
    {
        _inner = inner;
        _lock = @lock;

        // entering lock in constructor
        Monitor.Enter(_lock);
    }

    public T Current
    {
        get { return _inner.Current; }
    }

    object IEnumerator.Current
    {
        get { return Current; }
    }

    public void Dispose()
    {
        // .. and exiting lock on Dispose()
        // This will be called when foreach loop finishes
        Monitor.Exit(_lock);
    }

    /// <remarks>
    /// we just delegate actual implementation
    /// to the inner enumerator, that actually iterates
    /// over some collection
    /// </remarks>
    public bool MoveNext()
    {
        return _inner.MoveNext();
    }

    public void Reset()
    {
        _inner.Reset();
    }
}

Thread-safe List<T>

/// <summary>
/// A thread-safe IList implementation using the custom SafeEnumerator class
/// See: http://www.codeproject.com/KB/cs/safe_enumerable.aspx
/// </summary>
public class SafeList<T> : IList<T>
{
    // the (thread-unsafe) collection that actually stores everything
    private readonly List<T> _inner;

    // this is the object we shall lock on.
    private readonly object _lock = new object();

    public SafeList()
    {
        _inner = new List<T>();
    }

    public int Count
    {
        get
        {
            lock (_lock)
            {
                return _inner.Count;
            }
        }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public T this[int index]
    {
        get
        {
            lock (_lock)
            {
                return _inner[index];
            }
        }
        set
        {
            lock (_lock)
            {
                _inner[index] = value;
            }
        }
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        lock (_lock)
        {
            // instead of returning an usafe enumerator,
            // we wrap it into our thread-safe class
            return new SafeEnumerator<T>(_inner.GetEnumerator(), _lock);
        }
    }

    /// <remarks>
    /// To be actually thread-safe, our collection must be locked on all other operations
    /// </remarks>
    public void Add(T item)
    {
        lock (_lock)
        {
            _inner.Add(item);
        }
    }

    public void Clear()
    {
        lock (_lock)
        {
            _inner.Clear();
        }
    }

    public bool Contains(T item)
    {
        lock (_lock)
        {
            return _inner.Contains(item);
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        lock (_lock)
        {
            _inner.CopyTo(array, arrayIndex);
        }
    }

    public bool Remove(T item)
    {
        lock (_lock)
        {
            return _inner.Remove(item);
        }
    }

    public IEnumerator GetEnumerator()
    {
        lock (_lock)
        {
            return new SafeEnumerator<T>(_inner.GetEnumerator(), _lock);
        }
    }

    public int IndexOf(T item)
    {
        lock (_lock)
        {
            return _inner.IndexOf(item);
        }
    }

    public void Insert(int index, T item)
    {
        lock (_lock)
        {
            _inner.Insert(index, item);
        }
    }

    public void RemoveAt(int index)
    {
        lock (_lock)
        {
            _inner.RemoveAt(index);
        }
    }

    public ReadOnlyCollection<T> AsReadOnly()
    {
        lock (_lock)
        {
            return new ReadOnlyCollectio<T>(this);
        }
    }

    [CheckParameters]
    public void ForEach([NotNull] Action<T> action)
    {
        lock (_lock)
        {
            foreach (var item in _inner)
            {
                action(item);
            }
        }
    }

    [CheckParameters]
    public bool Exists([NotNull] Predicate<T> match)
    {
        lock (_lock)
        {
            foreach (var item in _inner)
            {
                if (match(item))
                {
                    return true;
                }
            }
        }
        return false;
    }
}
You can see I’ve used the CheckParameters and NotNull attributes for parameter validation using PostSharp, you can easily substitute them with normal null checks.

Thread-safe IEnumerable<T>

/// <summary>
/// A thread-safe IEnumerable implementation
/// See: http://www.codeproject.com/KB/cs/safe_enumerable.aspx
/// </summary>
public class SafeEnumerable<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> _inner;
    private readonly object _lock;

    public SafeEnumerable(IEnumerable<T> inner, object @lock)
    {
        _lock = @lock;
        _inner = inner;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new SafeEnumerator<T>(_inner.GetEnumerator(), _lock);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

 

Learn to build Production-Ready Serverless applications

Want to learn how to build Serverless applications and follow best practices? Subscribe to my newsletter and join over 5,000 AWS & Serverless enthusiasts who have signed up already.

3 thoughts on “Thread-safe enumeration in C#”

  1. Just want to point out that your implementation is flawed, try this:

    foreach(int value in safeList)
    {
    safeList.Remove(value);
    }

  2. Just would like to share with others that nowadays this approach is not a good idea. You might not even notice the problem in the beginning but if you use async/await inside the foreach enumeration Monitor.Exit might be called on a different from the Monitor.Enter thread which leads to an unpredictable exception.

Leave a Comment

Your email address will not be published. Required fields are marked *