views:

298

answers:

2

I find System.Monitor very confusing, although I understand threading, locks, deadlocks, race conditions, dining philosophers and all that jazz. Normally I use a ManualResetEvent() to do inter-thread co-ordination, but I know that that's a heavyweight kernel object, and that System.Monitor (Enter/Pulse, etc.) is much more efficient. I've Googled and Googled but cannot find a sensible example.

I would be most grateful if the SO crew could explain this potentially wonderful construct to me :-)

+8  A: 

Here's a very simple example; the call to Wait releases the lock (allowing Worker to obtain it) and adds the Main thread to the lock-object's pending queue. Worker then obtains the lock, and calls Pulse: this moves the Main thread into the lock-object's ready queue. When Worker releases the lock, Main can resume work.

Note that lock(obj) {...} is just compiler-candy for Monitor.Enter/Monitor.Exit in a try/finally block.

[edit: I changed the sample to move lock(sync) earlier, to avoid the (unlikely) risk of a missed Pulse]

    static void Main()
    {
        object sync = new object();
        lock (sync)
        {
            ThreadPool.QueueUserWorkItem(Worker, sync);
            Console.WriteLine("Main sleeping");

            // wait for the worker to tell us it is ready
            Monitor.Wait(sync);
            Console.WriteLine("Main woke up!");
        }
        Console.WriteLine("Press any key...");
        Console.ReadKey();
    }
    static void Worker(object sync)
    {
        Console.WriteLine("Worker started; about to sleep");
        Thread.Sleep(5000);
        Console.WriteLine("Worker about pulse");
        lock (sync)
        { // notify Main that we did something interesting
            Monitor.Pulse(sync);
            Console.WriteLine("Worker pulsed; about to release lock");
        }
        Console.WriteLine("Worker all done");
    }
Marc Gravell
I don't think the above will work, looks like you'll deadlock over sync
Jan Bannister
@Jan: Where exactly do you think it will deadlock? hint: it works fine
Marc Gravell
@Jan: Further hint, look carefully at the docs for Monitor.Wait...
Jon Skeet
Or the explanation I added (to be fair, the explanation paragraph wasn't in the first version I posted)
Marc Gravell
Hahahaha, that's the exact mental process I went through when trying to figure out Monitor.Wait()/Monitor.Enter() code:"Surely that's a deadlock"The key piece of information is "the call to Wait releases the lock", which I didn't know. Thanks Marc!
endian
@endian : no problem. The bit I struggled with for a while is that it isn't Pulse() that re-starts Main, but when Worker *releases* the lock. Main still has to re-aquire the lock before it can continue.
Marc Gravell
+3  A: 

See if this part of my threading article helps... (the second half of that page). It implements a producer/consumer queue: when the producer produces something in the queue (and finds it was empty - as an optimisation), it pulses the monitor to wake up any waiting threads. When a consumer tries to consume from the queue but finds it empty, it waits for a pulse before trying again.

Alternatively, Joe Albahari's threading tutorial has a section on Wait/Pulse too.

It's quite similar to the WaitHandle stuff you're used to - although frankly I find it easier to get my head round than WaitHandles, mostly because it's so similar to the Java wait/notify that I "grew up" with :)

Jon Skeet
Thanks Jon, I had tried to figure it out from your fantastic site in the past, without success (and probably without enough effort).
endian
In that case I'm *very* interested to hear about how I could improve the existing explanation :) To be honest it's been a while since I've done anything with the threading stuff - it could do with an overhaul. Any particular bits of the Wait/Pulse explanation which were confusing?
Jon Skeet