I have a .NET 4 WCF service that maintains a thread-safe, in-memory, dictionary cache of objects (SynchronizedObject). I want to provide safe, concurrent access to read and modify both the collection and the objects in the collection. Safely modifying the objects and the cache can be accomplished with reader-writer locks.
I am running into trouble providing read access to an object in the cache. My Read method returns a SynchronizedObject, but I do not know how to elegantly ensure no other threads are modifying the object while WCF is serializing the SynchronizedObject.
I have tried placing the Read return clause inside the read-lock and setting a breakpoint in a custom XmlObjectSerializer. When the XmlObjectSerializer::WriteObject(Stream,object) method is called, a read-lock is not held on the SynchronizedObject.
I am specifically concerned with the following scenario:
- Thread A calls Read(int). Execution continues until just after the return statement. By this point, the finally has also been executed, and the read lock on the SynchronizedObject has been released. Thread A's execution is interrupted.
- Thread B calls Modify(int) for the same id. The write lock is available and obtained. Sometime between obtaining the write lock and releasing it, Thread B is interrupted.
- Thread A restarts and serialization continues. Thread B has a write-lock on the same SynchronizedObject, and is in the middle of some critical section, but Thread A is reading the state of the SynchronizedObject and thus returns a potentially invalid object to the caller of Read(int).
I see two options:
- Maintain a custom XmlObjectSerializer that grabs the read-lock before calling the base.WriteObject(Stream, object) method, and releases it after. I do not like this option because sub-classing and overriding a framework serialization function to perform a certain action if a the object to be serialized matches a certain type smells to me.
- Create a deep-copy of a SynchronizedObject in the Read method while the read-lock is held, release the lock, and return the deep copy. I do not like this option because there will be many sub-classes of SynchronizedObject that I would have to implement and maintain correct deep-copiers for and deep-copies could be expensive.
What other options do I have? How should I implement the thread-safe Read method?
I have provided a dummy Service below for more explicit references:
public class Service : IService
{
IDictionary<int, SynchronizedObject> collection = new Dictionary<int, SynchronizedObject>();
ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
public SynchronizedObject Read(int id)
{
rwLock.EnterReadLock();
try
{
SynchronizedObject result = collection[id];
result.rwLock.EnterReadLock();
try
{
return result;
}
finally
{
result.rwLock.ExitReadLock();
}
}
finally
{
rwLock.ExitReadLock();
}
}
public void ModifyObject(int id)
{
rwLock.EnterReadLock();
try
{
SynchronizedObject obj = collection[id];
obj.rwLock.EnterWriteLock();
try
{
// modify obj
}
finally
{
obj.rwLock.ExitWriteLock();
}
}
finally
{
rwLock.ExitReadLock();
}
}
public void ModifyCollection(int id)
{
rwLock.EnterWriteLock();
try
{
// modify collection
}
finally
{
rwLock.ExitWriteLock();
}
}
}
public class SynchronizedObject
{
public ReaderWriterLockSlim rwLock { get; private set; }
public SynchronizedObject()
{
rwLock = new ReaderWriterLockSlim();
}
}