Daniel Fortunov's Adventures in Software Development » Invalid Wait Handle
0 Comments- Add comment |
Back to Software Development Blog Written on 31-Jan-2010 by asquiOne of the obscure gems garnered from my current reading of the book Concurrent Programming on Windows (by Joe Duffy) is an insight into the INVALID_HANDLE_VALUE constant.
In Win32 programming, functions that return a HANDLE (such as CreateFile) may return INVALID_HANDLE_VALUE to indicate failure (sometimes). You can check for this return value and call GetLastError to find out why the operation failed.
In .NET functions typically indicate unexpected failure by throwing an exception. The endless dance of “Do something; Did it succeed? If not, why did it fail. Do something else; Did it succeed? …” is replaced by structured exception handling and constructs such as try-catch, which let you defer thinking about error scenarios until you want to, rather than thinking… about errors… at every… step… of… the… way.
So if .NET methods such as File.Open() will throw exceptions rather than returning INVALID_HANDLE_VALUE we have no need to expose INVALID_HANDLE_VALUE in the .NET BCL, right? Not quite.
In addition to being used as a magic return value indicating failure, INVALID_HANDLE_VALUE also has some magic powers with methods that accept a HANDLE as a parameter. Now, you won’t get any useful behaviour from passing INVALID_HANDLE_VALUE to CloseHandle, however there is a group of functions that let you provide an event handle, do some asynchronous work, and then signal your event to let you know the work has been completed.
Functions such as UnregisterWaitEx and DeleteTimerQueueTimer will cancel any pending registered wait operation or a timer-queue timer, however if a callback has already been triggered this will still run to completion. If you need to clean up any resources used by your callback, to avoid pulling the rug out from under its feet, you must first ensure that your callback is not still executing. To avoid having to manually introduce control synchronisation in your callback, UnregisterWaitEx and DeleteTimerQueueTimer let you provide an event handle which will be signalled when any executing callbacks have returned.
If you don’t want the overhead of allocating another event and then registering a wait on it (in order to perform the clean-up asynchronously, when the event is signalled) you can tell the function to block and wait for any executing collback functions to complete before returning by providing INVALID_HANDLE_VALUE for the wait handle.
Now the interesting part: Since we previously concluded that there is no need to expose INVALID_HANDLE_VALUE in the .NET BCL, how would we get this handy blocking behaviour from the .NET equivalents to the methods mentioned above: RegisteredWaitHandle.Unregister(WaitHandle) and System.Threading.Timer.Dispose(WaitHandle)?
The MSDN documentation makes no suggestion that this behaviour is even possible (not even in the preview documentation for .NET 4). I’m not sure if this is an oversight or an intentionally unsupported behaviour.
To work around this we can do a little poking around the BCL with Reflector:
So the only way to get at INVALID_HANDLE_VALUE in .NET is to subclass WaitHandle. We don't actually need to do anything in our subclass, mind you:
public class InvalidWaitHandle : WaitHandle { }
So there you have it, pretty convoluted but works like a charm!
Here’s the full version, with documentation and a cached instance:
using System.Threading; /// <summary> /// An inert wait handle that can be used to avoid allocating a real event in /// some situations. /// </summary> /// <remarks> /// <para> /// An <see cref="InvalidWaitHandle"/> can be provided to methods such as /// <see cref="RegisteredWaitHandle.Unregister(WaitHandle)"/> and /// <see cref="Timer.Dispose(WaitHandle)"/>. In this case, the function waits /// for all callback functions to complete before returning, rather than /// returning immediately and signalling the provided wait handle /// asynchronously. /// </para> /// <para> /// Internally, this results in the use of INVALID_HANDLE_VALUE when calling /// the underlying Win32 functions. /// </para> /// <para> /// For further information, see "Concurrent Programming on Windows" (First /// Edition, 2009) by Joe Duffy, p. 374, 377. /// </para> /// </remarks> public class InvalidWaitHandle : WaitHandle { static InvalidWaitHandle() { Instance = new InvalidWaitHandle(); } /// <summary> /// Gets a shared instance of <see cref="InvalidWaitHandle"/> which may /// be re-used. /// </summary> /// <remarks> /// Using this field allows a single <see cref="InvalidWaitHandle"/> to /// be re-used as opposed to creating a <c>new</c> instance at every call /// site. /// </remarks> /// <value>A shared instance of <see cref="InvalidWaitHandle"/> which may /// be re-used.</value> public static InvalidWaitHandle Instance { get; private set; } }