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 accomodate 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 syncronisation 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 SyncronisedList<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 SyncronisedList<int> list = new SyncronisedList<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();
}
}