Multithreaded Synchronised List<T> Multithreaded Synchronised List<T> multithreading multithreading

Multithreaded Synchronised List<T>


Are you aware of the built-in SynchronizedCollection<T> class?

It uses standard Monitor-based locking rather than ReaderWriterLockSlim. You'd need to profile to determine whether this makes a significant performance difference in your particular usage scenarios.


There are a couple of thread issues here.

1.I think the GetEnumerator functions exposes a thread issue here. They give away a reference to the innerCache that is not controlled by your locks.

Example where it may break down is if you have a thread doing a foreach over the list while another thread is removing or inserting elements.

The solution would be to copy the list and return an enumerator on that newly cloned list instead. The draw back would be memory issues if the list is long.

2.The Contains() and IndexOf() functions are more or less useless unless you have another locking method outside of the synchronised list.

Example: Thread A gets index of object, Thread B inserts/removed/updates that object, Thread A index is now stale.


I don't think this is a great idea really with a fully synchronised list. Write a customised version instead with limited functionality.

If you only need a queue or stack, implement that one with only the two or three necessary methods that are fully synchronised. If you need more functionality than that, use a List and have the different threads do the synchronisation.


This class solves all problems and makes your list 100% thread-safe.

Race conditions are avoided by using scopes that work just like transactions in databases.

Client code

List<T> unsafeList = ... var threadSafeList = new SyncronisedList(unsafeList);using (threadSafeList.EnterReadScope()) {   // all your sequential read operations are thread-safe}using (threadSafeList.EnterWriteScope()) {   // all sequential read/write operations are thread-safe}

Class code

public class SyncronisedList<T> : IList<T> {    private readonly ReaderWriterLockSlim _threadLock;    private readonly IList<T> _internalList;    public SyncronisedList() : this(new List<T>()) {    }    public SyncronisedList(IList<T> internalList) {        _internalList = internalList;        _threadLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);    }    private U Read<U>(Func<U> function) {        using (EnterReadScope())            return function();    }    private void Read(Action action) {        using (EnterReadScope())            action();    }    private U Write<U>(Func<U> function) {        using (EnterWriteScope())            return function();    }    private void Write(Action action) {        using (EnterWriteScope())            action();    }    public IDisposable EnterReadScope() {        return new Scope<T>(this, false);    }    public IDisposable EnterWriteScope() {        return new Scope<T>(this, true);    }    public T this[int index] {        get { return Read(() => _internalList[index]); }        set { Write(() => _internalList[index] = value); }    }    public int IndexOf(T item) { return Read(() => _internalList.IndexOf(item)); }    public void Insert(int index, T item) { Write(() => _internalList.Insert(index, item)); }    public void RemoveAt(int index) { Write(() => _internalList.RemoveAt(index)); }    public void Add(T item) { Write(() => _internalList.Add(item)); }    public void Clear() { Write(() => _internalList.Clear()); }    public bool Contains(T item) { return Read(() => _internalList.Contains(item)); }    public int Count { get { return Read(() => _internalList.Count); } }    public bool IsReadOnly { get { return Read(() => _internalList.IsReadOnly); } }    public void CopyTo(T[] array, int arrayIndex) { Read(() => _internalList.CopyTo(array, arrayIndex)); }    public bool Remove(T item) { return Write(() => _internalList.Remove(item)); }    public IEnumerator<T> GetEnumerator() { return Read(() => _internalList.GetEnumerator()); }    IEnumerator IEnumerable.GetEnumerator() { return Read(() => (_internalList as IEnumerable).GetEnumerator()); }    private class Scope<U> : IDisposable {        private readonly SyncronisedList<U> _owner;        private readonly bool _write;        internal Scope(SyncronisedList<U> owner, bool write) {            _owner = owner;            _write = write;            if (_write)                _owner._threadLock.EnterWriteLock();            else                _owner._threadLock.EnterReadLock();        }        public void Dispose() {            if (_write)                _owner._threadLock.ExitWriteLock();            else                _owner._threadLock.ExitReadLock();        }    }}