Jono
Jono

Reputation: 2054

ReaderWriterLockSlim and Pulse/Wait

Is there an equivalent of Monitor.Pulse and Monitor.Wait that I can use in conjunction with a ReaderWriterLockSlim?

I have a class where I've encapsulated multi-threaded access to an underlying queue. To enqueue something, I acquire a lock that protects the underlying queue (and a couple of other objects) then add the item and Monitor.Pulse the locked object to signal that something was added to the queue.

public void Enqueue(ITask task)
{
    lock (mutex)
    {
        underlying.Enqueue(task);
        Monitor.Pulse(mutex);
    }
}

On the other end of the queue, I have a single background thread that continuously processes messages as they arrive on the queue. It uses Monitor.Wait when there are no items in the queue, to avoid unnecessary polling. (I consider this to be good design, but any flames (within reason) are welcome if they help me learn otherwise.)

private void DequeueForProcessing(object state)
{
    while (true)
    {
        ITask task;
        lock (mutex)
        {
            while (underlying.Count == 0)
            {
                Monitor.Wait(mutex);
            }
            task = underlying.Dequeue();
        }
        Process(task);
    }
}

As more operations are added to this class (requiring read-only access to the lock protected underlying), someone suggested using ReaderWriterLockSlim. I've never used the class before, and assuming it can offer some performance benefit, I'm not against it, but only if I can keep the Pulse/Wait design.

Upvotes: 2

Views: 740

Answers (2)

Jono
Jono

Reputation: 2054

After playing around a bit (and after accepting Mark's challenge (I agree, my change isn't exactly basic)) I came up with this combination:

ReaderWriterLockSlim rwls;
AutoResetEvent are;
public void Enqueue(ITask task) {
    rwls.EnterWriteLock();
    try {
        underlying.Enqueue(task);
        if (underlying.Count == 1) {
          are.Set();
        }
    } finally {
        rwls.ExitWriteLock();
    }
}
private void DequeueForProcessing(object state) {
    while (true) {
        ITask task;
        rwls.EnterWriteLock();
        try {
            while (underlying.Count == 0) {
                rwls.ExitWriteLock();
                are.WaitOne();
                rwls.EnterWriteLock();
            }
            task = underlying.Dequeue();
        } finally {
            rwls.ExitWriteLock();
        }
        Process(task);
    }
}

To my untrained eye, it seems to fit the bill (though it's a little uglier, to be sure,) and it's not too different from the original version.

Upvotes: 0

Marc Gravell
Marc Gravell

Reputation: 1062780

No, basically. It is optimised to provide some specific/common scenarios. If you go outside those scenarios it won't work. If you need Pulse etc, use Monitor instead.

Upvotes: 1

Related Questions