Home > C#, WinRT > How to add async/await support to an existing library

How to add async/await support to an existing library

18/03/2013

Suppose we have an existing library that does some asynchronous operations, but it doesn’t follow the async/await pattern. To be more concrete, we could have something like this:

public class OperationEventArgs : EventArgs
{
    public int Result { get; set; }
}

public class MyLibrary
{
    public event EventHandler<OperationEventArgs> OperationCompleted;

    public void StartExecute()
    {
        // Simulates a long running task.
        Task.Run(() =>
            {
                Task.Delay(5000).Wait();

                // Raises the event to signal the end of the asynchronous operation.
                if (OperationCompleted != null)
                    OperationCompleted(this, new OperationEventArgs 
                                                { 
                                                    Result = 42 
                                                });
            });
    }
}

Or we could have a third-party library that we can’t modify (the source code couldn’t be available). In such situations, we can easily add support to the async/await pattern using the TaskCompletitionSource class that is part of the Task Parallel Library. It allows us to expose our operations as Task objects.

Referring to the previous example, we can write an extension method on the MyLibrary type:

public static Task<int> ExecuteAsync(this MyLibrary myLibrary)
{
    var tcs = new TaskCompletionSource<int>();

    EventHandler<OperationEventArgs> handler = null;
    handler = new EventHandler<OperationEventArgs>((s, e) => 
    {
        myLibrary.OperationCompleted -= handler;

        // When the event fires, we call the TrySetResult on
        // TaskCompletionSource, signaling that the Task has
        // finished its work and the result value is available.
        tcs.TrySetResult(e.Result);
    });

    // Registers for the OperationCompleted event.
    myLibrary.OperationCompleted += handler;
    
    // Starts the asynchronous operation. When it completes,
    // the OperationCompleted event fires.
    myLibrary.StartExecute();

    // Returns a Task object that allows to wait for the operation
    // to complete.
    return tcs.Task;
}

The use of the new ExecuteAsync method is straightforward:

private async void button_Click(object sender, RoutedEventArgs e)
{
    MyLibrary lib = new MyLibrary();
    int result = await lib.ExecuteAsync();

    // ...
}

Things are even simpler if, instead of an event, our library uses an Action to specify the code to execute when the operation completes:

public class MyLibrary
{
    public void StartExecute(Action<int> completeOperation)
    {
        // Simulates a long running task.
        Task.Run(() =>
            {
                Task.Delay(5000).Wait();

                // Calls the action to continue.
                completeOperation.Invoke(42);
            });
    }
}

Having that, our extension method becomes as follows:

public static class MyLibraryExtensions
{
    public static Task<int> ExecuteAsync(this MyLibrary myLibrary)
    {
        var tcs = new TaskCompletionSource<int>();
        
        myLibrary.StartExecute(
            (result) => 
            {
                // We call the TrySetResult on TaskCompletionSource,
                // signaling that the Task has finished its work and
                // the result value is available.
                tcs.TrySetResult(result);
            });

        // Returns a Task object that allows to wait for the operation
        // to complete.
        return tcs.Task;
    }
}

By calling TrySetResult, TrySetException or TrySetCancelled, we can cause the task to succeed, fail or cancel, respectively.

About these ads
Categories: C#, WinRT
  1. No comments yet.
  1. 31/03/2013 at 07:38
Comments are closed.
Follow

Get every new post delivered to your Inbox.

Join 27 other followers

%d bloggers like this: