Implementing a SynchronizationContext.SendAsync method

Stephen Toub - MSFT

I recently saw two unrelated questions, the answers to which combine to form a potentially useful code snippet.

The first question was about SynchronizationContext.  SynchronizationContext provides a Post method, which asynchronously schedules the supplied delegate and object state to be executed according to the SynchronizationContext’s whims.  However, Post returns void, so there’s no built-in way to know when the operation has completed.  SynchronizationContext also provides a Send method, which won’t return until the delegate has been executed; that means that while you do have a built-in way to know when the operation has completed, it’s also a synchronous operation, blocking the current thread.  The question then was whether there was a way to achieve the asynchronous behavior of Post but with the ability of Send to know when the operation has completed, e.g. an asynchronous Send:

public Task SendAsync(SendOrPostCallback d, object state);

The second question was about implementing asynchronous operations that return Tasks but without using async and await.  In effect, the questioner wanted to create a Task-returning method to represent an existing non-Task-based mechanism they were already using.

So, let’s try to satisfy both questions by implementing SendAsync using Post as the underlying mechanism that we’ll wrap with a Task.

The first thing we need is a TaskCompletionSource<T>.  TaskCompletionSource<T> is the mechanism within the Task Parallel Library that exists to create tasks that you can explicitly complete. Rather than creating a Task that’s associated with a delegate and whose completion is then tied to that delegate’s execution, TaskCompletionSource<T> create as task that’s not associated with a delegate, and it gives you methods like SetResult and SetException that allow you to complete the task when and how you see fit. 

This capability makes TaskCompletionSource ideal for wrapping existing operations. The idea is that we create a TaskCompletionSource<T> and we then use its Task property to get at the task that we can manually complete via the TaskCompletionSource.  We then launch the underlying operation being wrapped, and when it completes, we either invoke the SetResult method with the result of the operation completing successfully, or we invoke the SetException method with the error that resulted from its failure. We can follow this pattern to implement SendAsync by wrapping Post:

public static Task SendAsync(
    this SynchronizationContext context, SendOrPostCallback d, object state)
{
    var tcs = new TaskCompletionSource<bool>();
    context.Post(delegate {
        try { 
            d(state);
            tcs.SetResult(true);
        }
        catch(Exception e) { tcs.SetException(e); }
    }, null);
    return tcs.Task;
}

Here, the delegate we pass to Post invokes the original delegate, but it does so wrapped in a try/catch block.  If the invocation of the original delegate succeeds, we complete the Task successfully, and if an exception occurred from invoking the delegate, we complete the task with the thrown error. With this extension method on SynchronizationContext, we can now write code that uses await to asynchronously wait for an operation sent to a particular SynchronizationContext to complete.  And while there are various things we could do to improve the performance of this method slightly (e.g. by taking advantage of Post’s object state parameter rather than using a closure), overall it’s a fine representation of how you can create Task-based wrappers for existing mechanisms.

0 comments

Discussion is closed.

Feedback usabilla icon