Home All Groups Group Topic Archive Search About

SynchronizationAttribute and Monitor.Wait deadlock

Author
27 Apr 2006 2:54 AM
Chris Chilvers
I've found the docs to be rather scant on exactly what the second parameter to WaitHandle.WaitOne(Int32, Boolean) was
for. The one named exitContext so I started playing with the synchronize attribute to find out how it all works.

I'm puzzled at why this blocks until it times out.

What is supposed to happen is the consumer thread is started and waits for a producer to signal that something is ready.
This behavior is true if reEntrant == true.

What actually happens is that the consumer never releases its context lock on the object so the producer is unable to
enter the object whilst the consumer is waiting.

So what is the difference? Why can't Monitor.Wait release its lock on the context unless reEntrant == true?


class Program
{
    public static void Main()
    {
        Tester test = new Tester();
        Thread t1 = new Thread(new ThreadStart(test.Consume));
        Thread t2 = new Thread(new ThreadStart(test.Produce));

        t1.Start();

        //ensure our consumer gets a lock and waits before starting the producer
        Thread.Sleep(10);
        t2.Start();

        t1.Join();
        t2.Join();

        Console.ReadKey(true);
    }
}

//note: that reEntrant == false, if it's set to true there is no problem
[Synchronization(SynchronizationAttribute.REQUIRES_NEW, false)]
public class Tester : ContextBoundObject
{
    private object resource_lock = new object();

    //this method will not run on the second thread until the consumer times out and the first thread
    //leaves the class (and thus the context).
    public void Produce()
    {
        Console.WriteLine("Tester begin produce: ThreadId = " + Thread.CurrentThread.ManagedThreadId);
        lock (resource_lock)
        {
            //just pretend we are doing something then signal the consumer
            Thread.Sleep(1000);
            Monitor.Pulse(resource_lock);
        }
        Console.WriteLine("Tester end produce: ThreadId = " + Thread.CurrentThread.ManagedThreadId);
    }

    public void Consume()
    {
        Console.WriteLine("Tester begin consume: ThreadId = " + Thread.CurrentThread.ManagedThreadId);
        lock (resource_lock)
        {
            //This will block indefinitely without the timeout, but should this not release the context?
            if (Monitor.Wait(resource_lock, 5000, true)) {
                Console.WriteLine("Tester, Item consumed");
            } else {
                Console.WriteLine("Tester, TIMED OUT");
            }
        }
        Console.WriteLine("Tester end consume: ThreadId = " + Thread.CurrentThread.ManagedThreadId);
    }
}


OUTPUT:
Tester begin consume: ThreadId = 4
Tester, TIMED OUT
Tester end consume: ThreadId = 4
Tester begin produce: ThreadId = 5
Tester end produce: ThreadId = 5

EXPECTED OUTPUT: (this behavior is exibited if reEntrant == true)
Tester begin consume: ThreadId = 4
Tester begin produce: ThreadId = 5
Tester end produce: ThreadId = 5
Tester, Item consumed
Tester end consume: ThreadId = 4

AddThis Social Bookmark Button