Does Monitor.Exit yield to waiting threads?

  • Thread starter Thread starter bughunter
  • Start date Start date
B

bughunter

Hi,

Consider this code:

----
Monitor.Pulse(oLock);
Monitor.Exit(oLock);
----

If a thread was waiting on oLock then will the current thread
immediately yield following the call to Exit() to allow other
threads(including the thread that was waiting) to do some work? Or will
they have to wait to be handed a timeslice according to the scheduling
algorithm's normal operation?

Thus if I wanted to give the waiting thread a greater chance of
obtaining a time slice as soon as the lock is released I was thinking
of doing this:

----
Monitor.Pulse(oLock);
Monitor.Exit(oLock);
Thread.Sleep(0); // yield to another thread.
----


I realise this may be picking hairs but I have a CPU bound worker
thread that interacts closely with a GUI [thread] and to prodcue a
smoother user experience I have used Thread.Sleep(0) in a few places to
allow the GUI thread to process its message queue more often than
normal, this works very nicely. Reducing the worker thread's priority
helped a little but not as much as a few carefully placed calls to
Thread.Sleep(0). Now I have one scenario where the GUI thread is very
briefly waiting on the worker thread, and I'm wondering if I should
yield to get the absolute best resposiveness or whether this happens
anyway.

Thanks,

Colin Green
 
For the record I had a think about this and came up with the following
piece of code to test what is happening:

---------------------------
class Class1
{
volatile int counter=0;
int snapshot;
object oLock = new object();

[STAThread]
static void Main(string[] args)
{
Class1 oClass1 = new Class1();
oClass1.Run();
}

public void Run()
{
Thread oThread1 = new Thread(new ThreadStart(ThreadOne));
Thread oThread2 = new Thread(new ThreadStart(ThreadTwo));

oThread1.Start();
oThread2.Start();
}

private void ThreadOne()
{
Monitor.Enter(oLock);

for(counter=0; counter<10000000; counter++)
{
if(counter==5000000)
{
Monitor.Pulse(oLock);
Monitor.Exit(oLock);
Thread.Sleep(0);
}
}
}

private void ThreadTwo()
{
// Wait for thread 1 to signal us.
Monitor.Enter(oLock);

snapshot = counter;

Monitor.Pulse(oLock);
Monitor.Exit(oLock);

Console.WriteLine("counter=" + snapshot);
}
}
---------------------------

Basically ThreadOne() counts to 10 million and releases a lock at 5
million. ThreadTwo() waits for the lock to be released and takes a
snapshot of the counter. Without the call to Thread.Sleep(0) the
snapshot value is typically around 5,000,2500
plus or minus a few hundred, just occasionally returning exactly
5,000,000.

With the call to Thread.Sleep(0) the snapshot value is always
5,000,000. I realise that without being explicitly stated in the CLR
spec or whatever that this sort behaviour may be implememtation or
platform specific, e.g. does the Mono runtime work this way? But for
now Thread.Sleep(0) works for me!

Colin.
 
Colin,

I've seen nothing in the documentation that indicates that Monitor.Exit
will relinquish the remainder of its time slice to other threads nor
have I observed behavior that suggests it is happening.

Brian
 
Hi Brian,

Indeed, but sometimes it's unclear as to whether some action is implied
by the nature of a method. So in this particular case perhaps the
documentation should state that the current timeslice will not
explicitly be relinquished following an Exit().

Regards,

Colin.
 
Colin,

I'd say a lot of the time the behavior of a method is unclear because
the documentation is so coarse in some areas, especially in the area of
threading. So, I do see where you're coming from.

Brian
 
Indeed, but sometimes it's unclear as to whether some action is implied
by the nature of a method. So in this particular case perhaps the
documentation should state that the current timeslice will not
explicitly be relinquished following an Exit().

But at that point, the class wouldn't be flexible - MS would be
*forced* never to explicitly relinquish the timeslice following a call
to Exit(), even if they later decided it would be a good idea.

There's always a very difficult balance between specifying *exactly*
what a method does, so that its behaviour can be more closely
predicted, and leaving it loose enough to allow for better alternative
implementations which still achieve the same main aim in the future.
 
Back
Top