Visual Studio 2010!

Read now >

View Now
DevSource RSS FEEDS
XML Want an easy way to keep up with breaking tech news? And the Get DevSource headlines delivered to your desktop with RSS.
ADVERTISEMENT
ADVERTISEMENT

 

DevSource.com: Your Source for Visual Studio on Facebook
ADVERTISEMENT
Waitable Timers in .NET with C#
By Jim Mischel

Rate This Article: Add This Article To:

Waitable Timers in .NET with C#
( Page 1 of 5 )

A waitable time is a synchronization object whose state is set to signaled when a specified due time arrives. In this article, Jim Mischel shows you how to get the most of waitable times using C#.

The .NET Framework includes classes that implement almost all of the Windows synchronization objects. Events, mutexes, semaphores, timers, and other Windows synchronization objects are accessible through managed wrappers found in the System.Threading namespace. One object that is noticeably absent is the waitable timer. That's unfortunate, because waitable timers are very useful objects.

A waitable timer is a synchronization object whose state is set to signaled when a specified due time arrives. Think of a .NET Timer object that sets an event. In fact, you can simulate some of what waitable timers do by using a Timer in conjunction with an AutoResetEvent or ManualResetEvent. For example, suppose you wanted to show a splash screen when your program starts up. You want the splash screen to be displayed while you're initializing data structures, but you want to make sure that it's displayed for at least five seconds, but no more than necessary.

One way to do that would be to create a ManualResetEvent and a one-shot timer set for a five second due time. When the timer expires, it sets the event. The code below illustrates that technique, although it uses console output to simulate displaying a splash screen.

 

 

 

 

 

 

 

 

static void Main(string[] args)
{
    ManualResetEvent splashEvent = new ManualResetEvent(false);
    using (Timer splashTimer = new Timer((s) =>
        {
            splashEvent.Set();
        }, null, 5000, Timeout.Infinite))
    {
        Console.WriteLine("Splash screen");
        // do initialization here.
        // If initialization takes less than 5 seconds,
        // the Wait below will hold splash visible for
        // a minimum of five seconds.
        splashEvent.WaitOne();
    }
    Console.Write("Press Enter:");
    Console.ReadLine();
}

If the initialization code takes longer than five seconds, then the timer will have set the event, and the call to splashEvent.WaitOne will not wait. If initialization takes less than five seconds, then the wait will delay until the timer due time arrives.

A waitable timer object combines the timer and the event into a single object so that, if such a thing existed, you could conceivably write:

    using (WaitableTimer splashTimer = new WaitableTimer(5000))
    {
        Console.WriteLine("Splash screen");
        // do initialization here
        splashTimer.WaitOne();
    }

If that's all you could do with a Windows waitable timer object, then I'd probably just implement my own object that wraps a Timer and a ManualResetEvent, and provides a simple interface. But waitable timers have some other functionality can't easily be duplicated using the existing .NET synchronization objects.

About Waitable Timers

As I mentioned above, a waitable timer is a synchronization object whose state is set to signaled when the due time arrives. As with events, mutexes, and semaphores, creating a waitable timer returns a handle that you can pass to WaitForSingleObject and other synchronization functions.

There are two types of timers that you can create: manual-reset and synchronization (auto-reset). The only difference between the two timers is that, once signaled, a manual-reset timer's state remains signaled until it is explicitly reset by user code. An auto-reset timer's state is reset to unsignaled after one thread performs a wait operation on it.

If you think of a waitable timer as a gate, then signaling a manual-reset timer would be like opening the gate so that people can pass freely through, until the gate is closed again. An auto-reset timer would be akin to having a gate guard who lets one person through and then closes the gate until told to open it again.

Both types of timers can be configured as one-shot or as periodic timers. A one-shot timer is signaled only once. A periodic timer is signaled at a regular user-defined interval, until canceled.

Because waitable timers are system-defined synchronization objects, they can be shared with other processes. All you need to do is create a named waitable timer object. If the named object already exists on the system, then your program will get a handle to that object and it will be shared among the processes. If the object doesn't exist, it's created in such a way that it can be shared among multiple processes.

All of those functions can be simulated by existing .NET synchronization objects. A Timer combined with an EventWaitHandle would work very nicely. Even the cross-process functionality can be mostly simulated by using a named event, although the timer part will exist only in the creating process. But waitable timers can do a little bit more.

As with the .NET Timer object, a waitable timer can be configured to execute a callback function when the due time arrives. Unlike the Timer callback, which is executed on a thread pool thread, the waitable timer's callback is executed on the thread that created the timer, using the asynchronous procedure call (APC) mechanism. This feature makes for some interesting possibilities but also requires that the thread be in what's called an "alertable wait state." In general, a thread enters an alertable wait state when it calls Sleep, WaitForSingleObject, or one of the other wait functions.

The last thing that a waitable timer can do is a real good one: it can wake your computer from a suspended state in order to signal the event and execute the callback function. Provided, of course, that you have the proper permissions and that your computer actually supports the restore functionality (i.e. has advanced power management).

Working with the API

Whenever I make Windows API subsystems available to managed code, I do it in two steps. First I create a class that contains managed prototypes for the unmanaged functions and make sure that I can use the API directly. Then I build .NET wrapper classes that make a more object-oriented interface that is easier to use from my C# programs.

The waitable timers API consists of just a handful of functions to create and modify timers. The functions are:

CreateWaitableTimerCreates a timer with default access.
CreateWaitableTimerExCreates a timer, allowing you to request specific access.
OpenWaitableTimerOpens an existing waitable timer.
SetWaitableTimerSets the due time, period, and callback, and activates the timer.
CancelWaitableTimerDeactivates the timer.

The API also includes a handful of constants, and it references a few other API functions (CloseHandle, for example) that I included in the interface class. The entire class, Win32WaitableTimer, is available in the code download for this article.

You'll notice that I used SafeWaitHandle rather than IntPtr in the API functions that expect or return handles. For example, the Windows API definition for CreateWaitableTimer is:

HANDLE WINAPI CreateWaitableTimer(
  __in          LPSECURITY_ATTRIBUTES lpTimerAttributes,
  __in          BOOL bManualReset,
  __in          LPCTSTR lpTimerName
);

My corresponding managed prototype is:

public static extern SafeWaitHandle CreateWaitableTimer(
    SECURITY_ATTRIBUTES timerAttributes,
    bool manualReset,
    string timerName);

This is in keeping with the .NET design guidelines. Safe handles help avoid a number of problems that can occur when working with naked handles, and SafeWaitHandle simplifies working with handles that are used with wait functions. For more information about why you should use safe handles, see the .NET Security Blog entry.

Creating and starting a timer is a two-step process. First you create the timer, specifying its type (manual- or auto-reset) and access permissions, and optionally assigning it a name. After the timer is created, you call SetWaitableTimer to set the due time, the period, the callback function and state, and specify whether you want the timer to wake the system from a suspended state.

Creating a simple timer is straightforward. For example, the code below creates a local (non-shared) manual-reset timer using default access and security permissions, and then disposes it when finished.

SafeWaitHandle myTimer =
    Win32WaitableTimer.CreateWaitableTimer(null, true, null);
if (myTimer.IsInvalid)
{
    Console.WriteLine("Windows API error creating timer ({0})",
        Marshal.GetLastWin32Error());
}
try
{
    // do processing here.
}
finally
{
    myTimer.Dispose();
}

Like all synchronization objects, a waitable timer is a system resource. You should release them when you're done using them. SafeWaitHandle makes that very easy because it implements IDisposable. As you can see from the code above, I wrap the code in a try/finally block, and call Dispose on the returned timer handle when I'm done processing.

Once you've created a waitable timer, you have to give it a due time before it can do any work. That's done by calling SetWaitableTimer: a function that has many different options. The function's prototype is:

[DllImport("kernel32", SetLastError = true)]
public static extern bool SetWaitableTimer(
    SafeHandle hTimer,
    ref long dueTime,
    int period,
    TimerAPCProc completionRoutine,
    IntPtr completionArg,
    bool fResume);

The first parameter is easy enough. It's the timer handle returned by CreateWaitableTimer. The next parameter, dueTime is a 64 bit integer that represents the time when the event should fire. There are two ways to specify the time: as an absolute time (1:59 AM on March 14), or as a relative time (one hour from now). SetWaitableTimer tells the difference by the sign of the argument. If dueTime is positive, it's interpreted as an absolute Universal time (i.e. UTC). If dueTime is negative, it's interpreted as a relative time.

In either case, dueTime is expressed as a number of 100-nanosecond ticks. Fortunately, DateTime and TimeSpan objects both have a Ticks property that returns the number of 100-nanosecond ticks that represent the value.

So, if you want the timer's due time to be 1:59 AM on March 14, here's how you'd create the dueTime value:

DateTime dtDue = new DateTime(2009, 3, 14, 1, 59, 00);
long dueTime = dtDue.ToUniversalTime().Ticks;

f you want the timer to expire an hour from now, you create a TimeSpan that represents that relative time, convert it to ticks, and negate it:

TimeSpan tsDue = TimeSpan.FromHours(1);
long dueTime = -(tsDue.Ticks);

If you forget to negate the value that you're using for a relative time, SetWaitableTimer will interpret it as a date in the past and your timer will never expire.

By the way, the dueTime parameter must be passed by reference due to historical reasons. It appears that the convention in the Windows API when these objects were created was to pass 64 bit values as pointers (references) rather than by value. The documentation says that it's treated as a constant, though, so the API shouldn't change the value.

The period parameter is used to create a periodic timer. If period is 0, then the timer signals once and is deactivated. If it's greater than zero, then the timer will signal every period milliseconds. Passing a negative value for period will cause SetWaitableTimer to fail.

The next two parameters, completionRoutine and completionArg are used to specify the optional timer callback function that will be called when the timer signals, and the argument to pass to the callback function. The completionRoutine is a TimerAPCProc delegate:

public delegate void TimerAPCProc(
    IntPtr completionArg,
    UInt32 timerLowValue,
    UInt32 timerHighValue);

The first argument is the completionArg that you passed to SetWaitableTimer. The second and third arguments make up a 64 bit integer that represents the time (in UTC) when the timer signaled. You can convert that to a .NET DateTime in the current time zone like this:

long timerValue = timerHighValue;
timerValue = (timerValue << 32) | timerLowValue;
DateTime signalTime = new DateTime(timerValue).ToLocalTime();

Remember that the completion routine is placed in the APC queue of the thread that called SetWaitableTimer, and won't be dispatched until the thread enters an alertable wait state.

The completionArg can be difficult to use from .NET, since it involves getting a pointer to a structure and converting it to an IntPtr, or, if you want to pass a reference type, creating a GCHandle and pinning the object in memory so that it doesn't move around.

If the last parameter to SetWaitableTimer, fResume is True, then a system in a suspended power conservation mode will be restored when the timer is signaled. If the system doesn't support restore, the call to SetWaitableTimer will still succeed, but the Windows error code (obtained by calling Marshal.GetLastWin32Error) will be set to ERROR_NOT_SUPPORTED (50).

The most common use of SetWaitableTimer will probably be to set a one-shot or periodic timer, ignoring the completion routine and the resume state. For example, to set a timer to signal five seconds from the current time, you would write:

TimeSpan tsDue = TimeSpan.FromSeconds(5);
long dueTime = -(tsDue.Ticks);
if (!Win32WaitableTimer.SetWaitableTimer(myTimer, ref dueTime, 0, null, IntPtr.Zero, false))
{
    Console.WriteLine("Windows API error setting timer ({0})",
        Marshal.GetLastWin32Error());
}

In the Windows API, you wait for an object to be signaled by calling WaitForSingleObject, or one of the other wait functions. If we put all of those code samples together, we can duplicate the functionality of the first splash screen example, this time using a Windows waitable timer object:

try
{
    // Create the timer
    SafeWaitHandle myTimer =
        Win32WaitableTimer.CreateWaitableTimer(null, true, null);
    if (myTimer.IsInvalid)
    {
        throw new IOException(
            string.Format("Windows API error creating timer ({0})",
            Marshal.GetLastWin32Error()));
    }

    try
    {
        TimeSpan tsDue = TimeSpan.FromSeconds(5);
        long dueTime = -(tsDue.Ticks);
        if (!Win32WaitableTimer.SetWaitableTimer(myTimer, ref dueTime,
            0, null, IntPtr.Zero, false))
        {
            throw new IOException(
                string.Format("Windows API error setting timer ({0})",
                Marshal.GetLastWin32Error()));
        }

        Console.WriteLine("Splash screen");
        // do processing here.

        Win32WaitableTimer.WaitForSingleObject(myTimer,
            Win32WaitableTimer.INFINITE);

    }
    finally
    {
        myTimer.Dispose();
    }
}
catch (IOException ex)
{
    Console.WriteLine(ex.Message);
}

Granted, it's a bit ugly, but it works. We'll pretty it up once we wrap the waitable timer into a nice .NET class.

You can share timers with other processes by using named timers. If you call CreateTimer or CreateTimerEx and pass a non-null value for the timerName parameter, a system timer is created and any process that has sufficient permissions (meaning most local processes) can access that timer by referring to it by name. There are two ways that a process can open an existing timer.

The OpenWaitableTimer API function allows you to open a waitable timer that you know already exists. If the timer exists and your process has sufficient access rights, OpenWaitableTimer will return a handle that refers to it. If the timer doesn't exist or if you don't have sufficient access rights, the returned handle will be invalid.

The other way to open an existing timer is to call CreateTimer or CreateTimerEx, passing it the name of the timer. If a timer with that name already exists, then a valid handle is returned and the Windows error code will be set to ERROR_ALREADY_EXISTS.

We'll look more at sharing timers once we've developed the .NET WaitableTimer class.

Finally, the CancelWaitableTimer method sets a waitable timer to inactive. It stops the timer and, if there are any pending callbacks it removes them from the APC queue. Canceling the timer does not change the signaled state of the timer. So if there are threads waiting on the timer and you cancel it, those threads will continue to wait until the timer is reactivated and signaled. Similarly, if the timer is already in the signaled state, it remains signaled.

The important thing to remember about canceling a timer is that doing so could cause a deadlock if other threads are depending on it.

By now you should have a pretty good idea of what waitable timers do and how to work with them through the API. I find working directly with the API to be incredibly frustrating and much prefer the .NET class interface to Windows synchronization objects. With that in mind, it's time to start building the managed WaitableTimer class.



 
 
>>> More Microsoft Languages Articles          >>> More By Jim Mischel