Monday, January 12, 2009

Thread Safe Dictionary in C#

I have been working with a project where concurrent requests are accessing a Dictionary. If you check from msdn the Dictionary class http://msdn.microsoft.com/en-au/library/xfhwa508(vs.80).aspx is not Thread safe.

Thread Safety

Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

To overcome this one can use Hashtable.http://msdn.microsoft.com/en-us/library/system.collections.hashtable.aspx
Hashtable is thread safe for use by multiple reader threads and a single writing thread. It is thread safe for multi-thread use when only one of the threads perform write (update) operations, which allows for lock-free reads provided that the writers are serialized to the Hashtable. To support multiple writers all operations on the Hashtable must be done through the wrapper returned by the Synchronized method, provided that there are no threads reading the Hashtable object.

But for multiple writes you need to get the Synchronized Hashtable as follows.
Hashtable myHT = new Hashtable();
Hashtable mySyncdHT = Hashtable.Synchronized( myHT );

But to make the dictionary you have three things to do.
1) Create a sub-class of System.Collections.Generic.Dictionary
2) Create a utility class for modifying the collection safely
3) Creating your own thread-safe dictionary from scratch.


I have written written a utility class which keeps a instance of Dictionary and modify it in a thread safe manner with ReaderWriterLockSlim. Check the advantage of ReaderWriterLockSlim over ReaderWriterLock here.http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx.

Following is my code sample.

public class SerializableDictionary : IXmlSerializable
{

//This is the internal dictionary that we are wrapping
IDictionary dict = new Dictionary();

#region IDictionary members
[NonSerialized]
ReaderWriterLockSlim dictionaryLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);


public virtual bool Remove(TKey key)
{
dictionaryLock.EnterWriteLock();
try
{
return this.dict.Remove(key);
}
finally
{
dictionaryLock.ExitWriteLock();
}
}


public virtual bool ContainsKey(TKey key)
{
dictionaryLock.EnterReadLock();
try
{
return this.dict.ContainsKey(key);
} finally
{
dictionaryLock.ExitReadLock();
}
}


public virtual bool TryGetValue(TKey key, out TValue value)
{
dictionaryLock.EnterReadLock();
try
{
return this.dict.TryGetValue(key, out value);
}
finally
{
dictionaryLock.ExitReadLock();
}
}


public virtual TValue this[TKey key]
{
get
{
dictionaryLock.EnterReadLock();
try
{
return this.dict[key];
}
finally
{
dictionaryLock.ExitReadLock();
}
}
set
{
dictionaryLock.EnterWriteLock();
try
{
this.dict[key] = value;
}
finally
{
dictionaryLock.ExitWriteLock();
}
}
}


public virtual ICollection Keys
{
get
{
dictionaryLock.EnterReadLock();
try
{
return new List(this.dict.Keys);
}finally
{
dictionaryLock.ExitReadLock();
}
}
}


public virtual ICollection Values
{
get
{
dictionaryLock.EnterReadLock();
try
{
return new List(this.dict.Values);
}
finally
{
dictionaryLock.ExitReadLock();
}
}
}


public virtual void Clear()
{
dictionaryLock.EnterWriteLock();
try
{
this.dict.Clear();
}
finally
{
dictionaryLock.ExitWriteLock();
}
}


public virtual int Count
{
get
{
dictionaryLock.EnterReadLock();
try
{
return this.dict.Count;
}
finally
{
dictionaryLock.ExitReadLock();
}
}
}


public virtual bool Contains(KeyValuePair item)
{
dictionaryLock.EnterReadLock();
try
{
return this.dict.Contains(item);
}
finally
{
dictionaryLock.ExitReadLock();
}

}


public virtual void Add(KeyValuePair item)
{
dictionaryLock.EnterWriteLock();
try
{
this.dict.Add(item);
}
finally
{
dictionaryLock.ExitWriteLock();
}
}


public virtual void Add(TKey key, TValue value)
{
dictionaryLock.EnterWriteLock();
try
{
this.dict.Add(key, value);
}
finally
{
dictionaryLock.ExitWriteLock();
}
}


public virtual bool Remove(KeyValuePair item)
{
dictionaryLock.EnterWriteLock();
try
{
return this.dict.Remove(item);
}
finally
{
dictionaryLock.ExitWriteLock();
}
}


public virtual void CopyTo(KeyValuePair[] array, int arrayIndex)
{
dictionaryLock.EnterReadLock();
try
{
this.dict.CopyTo(array, arrayIndex);
}
finally
{
dictionaryLock.ExitReadLock();
}
}


public virtual bool IsReadOnly
{
get
{
return this.dict.IsReadOnly;
}
}


public virtual IEnumerator> GetEnumerator()
{
throw new NotSupportedException("Cannot enumerate a threadsafe dictionary. Instead, enumerate the keys or values collection");
}
#endregion
}