Multithreading: registered waits

In this post, we’re going to keep looking on how we can reuse the thread pool’s threads for executing specific tasks. This post is all about registered waits. A registered wait allows us to specify a callback that will be invoked when a predefined kernel object is signaled. In .NET, we register a wait callback through one of the several static overloads of the RegisterWaitForSingleObject method. Here’s one of those overloads:

public static RegisteredWaitHandle RegisterWaitForSingleObject(
                 WaitHandle waitObject,
                 WaitOrTimerCallback callBack, 
                 object state,
                 int millisecondsTimeOutInterval,
                 bool executeOnlyOnce)

The first paramater (waitObject) lets you pass a reference to one of the existing kernel object (ie, one of the several derived WaitHandle classes). The second parameter is a delegate of type WaitOrTimerCallback:

public delegate void WaitOrTimerCallback(object state, bool timedOut);

As you can see, any compatible method receives a reference to the state parameter that you’ve passed to the RegisterWaitForSingleObject. The timeOut parameter that is passed to the callback method lets you know if the the callback is being executed because the wait timed out (which happens whenever the millisecontsTimeOutInterval you specified on the RegisterWaitForSingleObject ends). Finally, if you’re registering a wait on a object that stays signaled, you’ll be passing true to the executeOnlyOnce parameter (or you’ll get a never ending callback execution!).

Besides the RegisterWaitForSingleObject,there’s also an UnsafeRegisterWaitForSingleObject. The difference is that the unsafe method won’t propagate the context to the thread that handles the callback. Whatever method you use,both end up getting an instance of the RegisterWaitHandle type.

You’ll end up using that return instance for cancelling a wait, which you do by calling the Unregister method. Even if you pass true to executeOnlyOnce, you should also call the Unregister method when you’re done (the docs say that it helps improve the performance of the GC).

After the Unregister method has been called, it’s guaranteed that no more callbacks will be queued. It’s also important to keep in mind that, if there were any calls already queued, then those methods will still be invoked.

When you’re done with the RegisterWaitHandle, you should release it by calling Dispose Unregister. If you want to be notified when all callbacks have finished, then you should pass a valid WaitHandle (it’s really similar to what we’ve seen in the previous Timer post). Don’t pass the same object you’ve used for the registered wait or you’ll get into trouble!

Internally, the thread pool will use the same thread for waiting on groups of 63 different kernel objects. When that number is reached, a new thread will be used for waiting on the next 63 objects. Internally, the “waiting” thread uses a “group” of 64 kernel objects (the 64th slot is used for an interval event object) on which it uses a wait any approach.

Unlike unmanaged code, where you can use the “waiting” thread for executing the registered callback (this behavior is specified through a flag, btw), in managed code all callbacks will always be dispatched on a separate worker thread. Btw, it’s a really bad idea to use  a Mutex for a registered wait because you cannot get the wait registration to run on the same thread that owns the Mutex.

And I guess that’s all I have time for today. On the next post, we’ll take a look at some code. Keep tuned…


~ by Luis Abreu on June 2, 2009.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: