Async-await and WaitHandles

2015/07/07

The TPL is great but sometimes you have to work within a framework which schedules background tasks for you without giving you access to a Task of some kind. I had a situation like the following

private void DoSomeWork(CancellationToken token)
{
    token.ThrowIfCancellationRequested();
    // do lots of work 
}

// some other code that might happen in a UI thread
AsyncWorkScheduler.Schedule(()=> DoSomeWork(token)); 

I didn’t really have access to how the background task was scheduled, and to be honest I didn’t really care. What I needed was a way to wait for the work to finish, whether it finished by itself or if it was cancelled via the CancellationToken. I wanted to use await to avoid blocking any UI threads as well.

I could use the AutoResetEvent as a way to perform cross-thread signaling to indicate the work is done. Simple enough

private readonly AutoResetEvent _autoReset = new AutoResetEvent(false);

private void DoSomeWork(CancellationToken token) 
{
    var cancellation = (CancellationToken)obj;
    try
    {               
        token.ThrowIfCancellationRequested();
        // do lots of work 
    }
    finally
    { 
        // yay! All done
        _autoReset.Set();
    }
}

So how would you use await with an AutoResetEvent?

/// <summary>
/// Create a task based on the wait handle, which is the base class
/// for quite a few synchronization primitives
/// </summary>
/// <param name="handle">The wait handle</param>
/// <returns>A valid task</returns>
private static Task WaitHandleAsTask(WaitHandle handle)
{
    return WaitHandleAsTask(handle, Timeout.InfiniteTimeSpan);
}

/// <summary>
/// Create a task based on the wait handle, which is the base class
/// for quite a few synchronization primitives
/// </summary>
/// <param name="handle">The wait handle</param>
/// <param name="timeout">The timeout</param>
/// <returns>A valid task</returns>
private static Task WaitHandleAsTask(WaitHandle handle, TimeSpan timeout)
{
    var tcs = new TaskCompletionSource<object>();
    var registration = ThreadPool.RegisterWaitForSingleObject(handle, (state, timedOut) =>
    {
        var localTcs = (TaskCompletionSource<object>)state;
        if (timedOut)
        {   
            localTcs.SetCanceled();
        }   
        else
        {
            localTcs.SetResult(null);
        }
    }, tcs, timeout, true);

    // clean up the RegisterWaitHandle
    tcs.Task.ContinueWith((_, state) => ((RegisteredWaitHandle)state).Unregister(null), registration, TaskScheduler.Default); 
    return tcs.Task;
}

ThreadPool.RegisterWaitForSingleObject performs a wait in a thread pool thread, and when the handle is signal, the delegate executes executes in a worker thread. This will signal the task completion.

So your wait operation ends up looking like

void async SomeAsyncMethod(){
// .. stuff and work
await WaitHandleAsTask(_autoReset);
// .. more stuff and work
}

Pretty neat eh?