AutoResetEvent and ManualResetEvent - using WaitHandles

This comes from a quick writeup I did for a group of intern and entry-level developers on an internal project (project [codename] -- yes, that's the real name). This particular project had a number of threads working on some various tasks and used WaitHandles in one critical section. Although most of them didn't need to work with the threading stuff ever, I still got questions on it. I wanted to encourage them all to understand everything -- especially if they took their own time to look at it -- so I wrote this (quickly). It's a bit old, but some questions came up with new people, and I thought "Why not share?"

Confused about WaitHandles? Sure you are. For one, they have the word "Event" on them.. Are they events? Do they call delegates?. For another, what's this "Reset" action, and why would I want to do it manually? And lastly, what does the documentation mean when it says "Signaled" and "Nonsignaled"? In project [codename] you see a good number of calls to WaitHandle-derived objects , both AutoReset and ManualReset. As the interaction can be somewhat complex, it can be difficult to decipher what is going on. Hopefully this simplified example will help clear things up.

If you have multiple threads in your application, chances are that you're going to want to control their execution at some point. I don't mean "make sure that only one thread increments a counter at one time", but I mean "have my worker thread(s) wait until something happens", or "have my worker threads go, but pause when something happens". Here's where the WaitHandles can really shine. In the example below I'm going to show the AutoResetEvent and ManualResetEvent in use, and explain what's going on and why, and what "signaled" and "nonsignaled" means.

Okay, lets meet our contrived sample application. It has the useful task of calculating the square root of lots of random numbers. Normally this would not be a good candidate for threads, but this is a contrived example -- so we will use threads. Because I want to control these threads to show the WaitHandles, I'm going to create two groups of threads. One group will be controlled by a ManualResetEvent, the other group by a AutoResetEvent.

Shown here is the application UI in question. Pressing the "Start" button will create the thread groups, and start them running. Pressing the "Set Manual" will call .Set on the ManualResetEvent, and pressing "Reset Manual" will call .Reset. Pressing the "Set Auto" will call .Set on the AutoResetEvent.

The ManualResetEvent

Lets focus on the ManualResetEvent. When I press start, a bunch of threads are created and passed a WaitHandle - for this group, a ManualResetEvent. The first thing they do is enter a loop where they

  1. Call .WaitOne() on the WaitHandle they were passed.
  2. Get a random number and calculate it's square root
  3. Sleep 0-2 seconds 
  4. Raise an event to let the UI know a new result was calculated

Pretty simple loop, eh? Now, when we press start, nothing happens. They don't do anything... why not? The answer lies in the Call to .WaitOne() in each loop. See, a WaitHandle has two states - signaled and non-signaled. Think of it as green light (signaled), and red light (non-signaled). When a thread calls WaitHandle.WaitOne(), it will block (wait, sleep, pause, whatever you want to think of it as) until the WaitHandle is signaled (green light). If it's already green, then the call to .WaitOne() will simply return right away. If it's red, then it will wait until someone turns the WaitHandle green before returning (or, you can specify a timeout to wait, and it will wait until either the timeout passes, or the handle is signaled, whichever is first).

So by using the buttons to set and reset the WaitHandle (set turns it green (signaled), reset turns it red (non-signaled)).  If you set it, they all run without pausing as step one above. If you reset it, they will run until the call to WaitOne(), then wait until you press "set". Once you do, they will all start going again.

The AutoResetEvent

Now that you have the ManualResetEvent down, and you understand what signaled, non-signaled and blocked mean, let's move on to the AutoResetEvent. Now, in the sample application, the same code is used for both groups of example threads. That means that the loop is exactly the same. So we press "Start"... and nothing happens. The threads are all blocked on step one, calling WaitOne() on the AutoResetEvent they were passed.

So press the "Set Auto" button. One result is returned, then.... nothing! The threads are all blocked at step one again. Hey, what's going on?

The AutoResetEvent.Set() method doesn't really turn the WaitHandle green (signaled). What it does is release one waiting thread once. You can think of it as automatically calling its own Reset() (hence the name AutoResetEvent) as soon as it has let someone through the call to Waitxxx() .

Wrap-up

So I hope that helps you on your way to understanding WaitHandles. We didn't touch timeouts, Mutex's or Synchronization domains at all, but hopefully the documentation on these will be much clearer now. If not, let me know or stop by.

You can get this sample application source code here, or here.