One of the many things missing from the Silverlight API is a ReaderWriterLock implementation, though it is not a day-to-day requisite yet sometimes you really need it. The good news is, the underpinnings for it's implementation are all their, basically carried in-tact from the desktop counterpart.

I found a very good implementation by Vance Morrison, which I understand forms the basis of the ReaderWriterLockSlim in .NET 3.5 using spin locks.  Below is the Sliverlight version of the same, with some minute changes:

/// <summary>
/// A reader-writer lock implementation that is intended to be simple, yet very
/// efficient.  In particular only 1 interlocked operation is taken for any lock 
/// operation (we use spin locks to achieve this).  The spin lock is never held
/// for more than a few instructions (in particular, we never call event APIs
/// or in fact any non-trivial API while holding the spin lock).   
/// 
/// Currently this ReaderWriterLock does not support recurision, however it is 
/// not hard to add 
/// </summary>
/// <remarks>
/// By Vance Morrison
/// Taken from - http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
/// Code at - http://blogs.msdn.com/vancem/attachment/563180.ashx
/// </remarks>
public class ReaderWriterLock
{
    // Lock specifiation for myLock:  This lock protects exactly the local fields associted
    // instance of MyReaderWriterLock.  It does NOT protect the memory associted with the
    // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
    int myLock;

    // Who owns the lock owners > 0 => readers
    // owners = -1 means there is one writer.  Owners must be >= -1.  
    int owners;

    // These variables allow use to avoid Setting events (which is expensive) if we don't have to. 
    uint numWriteWaiters;        // maximum number of threads that can be doing a WaitOne on the writeEvent 
    uint numReadWaiters;         // maximum number of threads that can be doing a WaitOne on the readEvent
    uint numUpgradeWaiters;      // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1). 

    // conditions we wait on. 
    EventWaitHandle writeEvent;    // threads waiting to aquire a write lock go here.
    EventWaitHandle readEvent;     // threads waiting to aquire a read lock go here (will be released in bulk)
    EventWaitHandle upgradeEvent;  // thread waiting to upgrade a read lock to a write lock go here (at most one)

    public ReaderWriterLock()
    {
        // All state can start out zeroed. 
    }

    public void AcquireReaderLock(int millisecondsTimeout)
    {
        EnterMyLock();
        for (; ; )
        {
            // We can enter a read lock if there are only read-locks have been given out
            // and a writer is not trying to get in.  
            if (owners >= 0 && numWriteWaiters == 0)
            {
                // Good case, there is no contention, we are basically done
                owners++;       // Indicate we have another reader
                break;
            }

            // Drat, we need to wait.  Mark that we have waiters and wait.  
            if (readEvent == null)      // Create the needed event 
            {
                LazyCreateEvent(ref readEvent, false);
                continue;   // since we left the lock, start over. 
            }

            WaitOnEvent(readEvent, ref numReadWaiters, millisecondsTimeout);
        }
        ExitMyLock();
    }

    public void AcquireWriterLock(int millisecondsTimeout)
    {
        EnterMyLock();
        for (; ; )
        {
            if (owners == 0)
            {
                // Good case, there is no contention, we are basically done
                owners = -1;    // indicate we have a writer.
                break;
            }

            // Drat, we need to wait.  Mark that we have waiters and wait.
            if (writeEvent == null)     // create the needed event.
            {
                LazyCreateEvent(ref writeEvent, true);
                continue;   // since we left the lock, start over. 
            }

            WaitOnEvent(writeEvent, ref numWriteWaiters, millisecondsTimeout);
        }
        ExitMyLock();
    }

    public void UpgradeToWriterLock(int millisecondsTimeout)
    {
        EnterMyLock();
        for (; ; )
        {
            Debug.Assert(owners > 0, "Upgrading when no reader lock held");
            if (owners == 1)
            {
                // Good case, there is no contention, we are basically done
                owners = -1;    // inidicate we have a writer. 
                break;
            }

            // Drat, we need to wait.  Mark that we have waiters and wait. 
            if (upgradeEvent == null)   // Create the needed event
            {
                LazyCreateEvent(ref upgradeEvent, false);
                continue;   // since we left the lock, start over. 
            }

            if (numUpgradeWaiters > 0)
            {
                ExitMyLock();
                throw new InvalidOperationException("UpgradeToWriterLock already in process.  Deadlock!");
            }

            WaitOnEvent(upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout);
        }
        ExitMyLock();
    }

    public void ReleaseReaderLock()
    {
        EnterMyLock();
        Debug.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
        --owners;
        ExitAndWakeUpAppropriateWaiters();
    }

    public void ReleaseWriterLock()
    {
        EnterMyLock();
        Debug.Assert(owners == -1, "Calling ReleaseWriterLock when no write lock is held");
        Debug.Assert(numUpgradeWaiters > 0);
        owners++;
        ExitAndWakeUpAppropriateWaiters();
    }

    public void DowngradeToReaderLock()
    {
        EnterMyLock();
        Debug.Assert(owners == -1, "Downgrading when no writer lock held");
        owners = 1;
        ExitAndWakeUpAppropriateWaiters();
    }

    /// <summary>
    /// A routine for lazily creating a event outside the lock (so if errors
    /// happen they are outside the lock and that we don't do much work
    /// while holding a spin lock).  If all goes well, reenter the lock and
    /// set 'waitEvent' 
    /// </summary>
    private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
    {
        Debug.Assert(MyLockHeld);
        Debug.Assert(waitEvent == null);

        ExitMyLock();
        EventWaitHandle newEvent;
        if (makeAutoResetEvent)
            newEvent = new AutoResetEvent(false);
        else
            newEvent = new ManualResetEvent(false);
        EnterMyLock();
        if (waitEvent == null)          // maybe someone snuck in. 
            waitEvent = newEvent;
    }

    /// <summary>
    /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.  
    /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
    /// </summary>
    private void WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
    {
        Debug.Assert(MyLockHeld);

        waitEvent.Reset();
        numWaiters++;

        bool waitSuccessful = false;
        ExitMyLock();      // Do the wait outside of any lock 
        try
        {
            if (!waitEvent.WaitOne(millisecondsTimeout))
                throw new InvalidOperationException("ReaderWriterLock timeout expired");
            waitSuccessful = true;
        }
        finally
        {
            EnterMyLock();
            --numWaiters;
            if (!waitSuccessful)        // We are going to throw for some reason.  Exit myLock. 
                ExitMyLock();
        }
    }

    /// <summary>
    /// Determines the appropriate events to set, leaves the locks, and sets the events. 
    /// </summary>
    private void ExitAndWakeUpAppropriateWaiters()
    {
        Debug.Assert(MyLockHeld);

        if (owners == 0 && numWriteWaiters > 0)
        {
            ExitMyLock();      // Exit before signaling to improve efficiency (wakee will need the lock)
            writeEvent.Set();   // release one writer. 
        }
        else if (owners == 1 && numUpgradeWaiters != 0)
        {
            ExitMyLock();          // Exit before signaling to improve efficiency (wakee will need the lock)
            upgradeEvent.Set();     // release all upgraders (however there can be at most one). 
            // two threads upgrading is a guarenteed deadlock, so we throw in that case. 
        }
        else if (owners >= 0 && numReadWaiters != 0)
        {
            ExitMyLock();    // Exit before signaling to improve efficiency (wakee will need the lock)
            readEvent.Set();  // release all readers. 
        }
        else
            ExitMyLock();
    }

    private void EnterMyLock()
    {
        if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
            EnterMyLockSpin();
    }

    private void EnterMyLockSpin()
    {
        for (int i = 0; ; i++)
        {
            if (i < 3 && Environment.ProcessorCount > 1)
                Thread.SpinWait(20);    // Wait a few dozen instructions to let another processor release lock. 
            else
                Thread.Sleep(0);        // Give up my quantum.  

            if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
                return;
        }
    }
    private void ExitMyLock()
    {
        Debug.Assert(myLock != 0, "Exiting spin lock that is not held");
        myLock = 0;
    }

    private bool MyLockHeld { get { return myLock != 0; } }

}

This implementation features both timeouts and upgrading to writer locks, however it does not support recursion or try enter lock usage. Also, it's relatively fast, in some basic testings I did it ran 3x-4x v/s Monitor Locks, which compares well to other less feature-rich implementations (see here) that run 8x-10x v/s the generic Monitor Locks.  And just as a resource, you can read more about locks in general from this entry by Jeff Moser on his blog.

Posted by Rishi on 08-Mar-09 5:20 AM, 20 Comments

Categories: Code, Silverlight