Multithreaded Synchronised List<T>

c#, generics, list, multithreading

Solution

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.

Problem

I have a requirement whereby I needed to store a simple cache of a list of items. I was using List< T > for this purpose, but we have now changed the design to accommodate multiple threads. The architecture of the system is driven by events, therefore it's quite likely that a read and write operation could collide. Since the vast majority of access will be read-only I thought that the ReaderWriterLockSlim might be a good candidate. The cache only needs to be accurate at the point of reading for that moment in time. I have written the code below and it seems to work ok, but are there some potential pain points? UPDATE: Whilst the code below does fix some synchronisation problems it's not 100% perfect. I have since decided to implement a much simpler class that doesn't expose all of the IList< T > operations and therefore makes it 'safer' to re-use. ``` public class SynchronisedList<T> : IList<T> { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private IList<T> innerCache = new List<T>(); private U ReadReturn<U>(Func<U> function) { cacheLock.EnterReadLock(); try { return function(); } finally { cacheLock.ExitReadLock(); } } private void Read(Action action) { cacheLock.EnterReadLock(); try { action(); } finally { cacheLock.ExitReadLock(); } } private U WriteReturn<U>(Func<U> function) { cacheLock.EnterWriteLock(); try { return function(); } finally { cacheLock.ExitWriteLock(); } } private void Write(Action action) { cacheLock.EnterWriteLock(); try { action(); } finally { cacheLock.ExitWriteLock(); } } public T this[int index] { get { return ReadReturn(() => innerCache[index]); } set { Write(() => innerCache[index] = value); } } public int IndexOf(T item) { return ReadReturn(() => innerCache.IndexOf(item)); } public void Insert(int index, T item) { Write(() => innerCache.Insert(index, item)); } public void RemoveAt(int index) { Write(() => innerCache.RemoveAt(index)); } public void Add(T item) { Write(() => innerCache.Add(item)); } public void Clear() { Write(() => innerCache.Clear()); } public bool Contains(T item) { return ReadReturn(() => innerCache.Contains(item)); } public int Count { get { return ReadReturn(() => innerCache.Count); } } public bool IsReadOnly { get { return ReadReturn(() => innerCache.IsReadOnly); } } public void CopyTo(T[] array, int arrayIndex) { Read(() => innerCache.CopyTo(array, arrayIndex)); } public bool Remove(T item) { return WriteReturn(() => innerCache.Remove(item)); } public IEnumerator<T> GetEnumerator() { return ReadReturn(() => innerCache.GetEnumerator()); } IEnumerator IEnumerable.GetEnumerator() { return ReadReturn(() => (innerCache as IEnumerable).GetEnumerator()); } } internal class Program { private static SynchronisedList<int> list = new SynchronisedList<int>(); private static void Main() { for (int i = 0; i < 500000; i++) { var index = i; ThreadPool.QueueUserWorkItem((state) => { var threadNum = (int)state; if (threadNum % 10 == 0) { list.Add(threadNum); } else { Console.WriteLine(list.Count); } }, index); } Console.ReadKey(); } } ```

Original source