Thursday, September 30, 2010

Synchronization in .NET: ManualResetEvents and Monitors

I recently needed to simultaneously wake an arbitrary number of waiting threads, each waiting on some condition.  My first approach was to have a shared ManualResetEvent, a subclass of EventWaitHandle with the property that "Once it has been signaled, ManualResetEvent remains signaled until it is manually reset. That is, calls to WaitOne return immediately." (MSDN docs.)  [.NET also has AutoResetEvents, but with these only one waiting thread is woken when the event is signalled.]

The idiom I was using was this: I had a global ManualResetEvent called WakeUp; threads waiting on the condition would call WakeUp.WaitOne() (causing them to sleep until...); a thread identifying the waking condition would call WakeUp.Set(); WakeUp.Reset().  My reading of the MSDN documentation is that the call to Set() should wake all waiting threads, while the call to Reset() should reset the event handle for the next wake-up signal.

Tragically, this did not work and my program would randomly flounder.  If I added a delay between the Set() and Reset() calls, the likelihood of floundering reduced, but wasn't eliminated -- and, besides, that is too disgraceful a hack to contemplate for more than a moment.

The right solution was to use a Monitor.  The idiom here is as follows:

For Threads Waiting On Some Condition


Monitor.Enter(SyncObj);
while (!condition) { Monitor.Wait(SyncObj); }
Monitor.Exit(SyncObj);


which can be abbreviated to


lock (SyncObj) {
  while (!condition) { Monitor.Wait(SyncObj); }
}

For Threads Establishing Some Condition


Monitor.Enter(SyncObj);
// Establish condition.
Monitor.PulseAll(SyncObj);
Monitor.Exit(SyncObj);


which can be abbreviated to


lock (SyncObj) {
  // Establish condition.
  Monitor.PulseAll(SyncObj);
}


Peace at last...  (For the curious, in this particular application it would not have been sensible to set up a separate event handle for each condition, which is what one would normally do.)

No comments: