burkemw3
burkemw3

Reputation: 450

Providing concurrent read and write access to objects through WCF

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:

  1. 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.
  2. 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.
  3. 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:

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();
        }
    }

Upvotes: 1

Views: 1696

Answers (1)

John Fisher
John Fisher

Reputation: 22719

New answer

Based on your new information and clearer scenario, I believe you want to use something similar to functional programming's immutability feature. Instead of serializing the object that could be changed, make a copy that no other thread could possibly access, then serialize that.

Previous (not valuable) answer

From http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.enterwritelock.aspx:

If other threads have entered the lock in read mode, a thread that calls the EnterWriteLock method blocks until those threads have exited read mode. When there are threads waiting to enter write mode, additional threads that try to enter read mode or upgradeable mode block until all the threads waiting to enter write mode have either timed out or entered write mode and then exited from it.

So, all you need to do is call EnterWriteLock and ExitWriteLock inside ModifyObject(). Your attempt to make sure you have both a read and a write lock is actually stopping the code from working.

Upvotes: 1

Related Questions