Skip to content

Instantly share code, notes, and snippets.

@AArnott
Created July 15, 2011 15:55
Show Gist options
  • Save AArnott/1084951 to your computer and use it in GitHub Desktop.
Save AArnott/1084951 to your computer and use it in GitHub Desktop.
C# 5 Awaitable WaitHandle
public static class AwaitExtensions
{
/// <summary>
/// Provides await functionality for ordinary <see cref="WaitHandle"/>s.
/// </summary>
/// <param name="handle">The handle to wait on.</param>
/// <returns>The awaiter.</returns>
public static TaskAwaiter GetAwaiter(this WaitHandle handle)
{
Contract.Requires<ArgumentNullException>(handle != null);
return handle.ToTask().GetAwaiter();
}
/// <summary>
/// Creates a TPL Task that is marked as completed when a <see cref="WaitHandle"/> is signaled.
/// </summary>
/// <param name="handle">The handle whose signal triggers the task to be completed.</param>
/// <returns>A Task that is completed after the handle is signaled.</returns>
/// <remarks>
/// There is a (brief) time delay between when the handle is signaled and when the task is marked as completed.
/// </remarks>
public static Task ToTask(this WaitHandle handle)
{
Contract.Requires<ArgumentNullException>(handle != null);
Contract.Ensures(Contract.Result<Task>() != null);
var tcs = new TaskCompletionSource<object>();
var localVariableInitLock = new object();
lock (localVariableInitLock)
{
RegisteredWaitHandle callbackHandle = null;
callbackHandle = ThreadPool.RegisterWaitForSingleObject(
handle,
(state, timedOut) =>
{
tcs.SetResult(null);
// We take a lock here to make sure the outer method has completed setting the local variable callbackHandle.
lock (localVariableInitLock)
{
callbackHandle.Unregister(null);
}
},
state: null,
millisecondsTimeOutInterval: Timeout.Infinite,
executeOnlyOnce: true);
}
return tcs.Task;
}
}
@AArnott
Copy link
Author

AArnott commented Oct 23, 2020

It doesn't matter whether the threadpool thread is the same or different. What would matter is whether the callback could be executed directly by the RegisterWaitForSingleObject method before it returns (which would be on the same thread). And if so, spinning would deadlock because the assignment cannot be completed while my lock would throw NRE. So both would be broken.

But since the documentation is that it's queued to the threadpool, my lock is perfectly adequate. The spin approach would work too, perhaps, but is more complicated and may spin the CPU more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment