Create Task-based method from “legacy” callback method

March 20, 2012, (updated on November 5, 2015), 4 comments, Software Development

In my projects I have a lot of asynchronous methods with a callback as parameter which is called when the operation has completed. I asked myself how to port these methods to support the new async / await keywords and if possible allow me to use the class with older frameworks which do not support this new functionality. The samples in this article use the Http classes from my library project MyToolkit.

We start with a simple method called LoadHtml which will call the callback action completed after completion:

public void LoadHtml(String url, Action<HttpResponse> completed)
{
    Http.Get(url, completed);
}

To take advantage of the new async / await paradigm introduced in C# 5, we need to implement a new method, called LoadHtmlAsync (the naming convention for Task-based methods requires to add the postfix Async to the method name) which returns a Task object. To remove the callback method, you can use the TaskCompletionSource class which has the method SetResult to set the result after completion. The SetResult method will be called in the callback and will complete the current task:

#if WINRT
public Task<HttpResponse> LoadHtmlAsync(string url)
{
    var task = new TaskCompletionSource<HttpResponse>();
    LoadHtml(url, result => { task.SetResult(result); });
    return task.Task;
}
#endif

Now, the new method can easily be used with the await keyword:

var response = await LoadHtmlAsync("http://www.domain.com");

To minimize redundant code, I’ve developed a helper class (download) which encapsulates the previous shown mechanism:

public static class TaskHelper
{
    public static Task<TResult> RunCallbackMethod<TResult>(Action<Action<TResult>> func)
    {
        var task = new TaskCompletionSource<TResult>();
        func(result => { task.SetResult(result); });
        return task.Task;
    }

    public static Task<TResult> RunCallbackMethod<TResult, T1>(Action<T1, Action<TResult>> func, T1 a)
    {
        var task = new TaskCompletionSource<TResult>();
        func(a, result => { task.SetResult(result); });
        return task.Task;
    }

    public static Task<TResult> RunCallbackMethod<TResult, T1, T2>(Action<T1, T2, Action<TResult>> func, T1 a, T2 b)
    {
        var task = new TaskCompletionSource<TResult>();
        func(a, b, result => { task.SetResult(result); });
        return task.Task;
    }
}

This way the original class with the method LoadHtml can be extended with a WinRT version. Now it’s possible to create two library projects: One for example for Silverlight and one for WinRT with the same files (linked or included from the same directory). You simply have to add the “WINRT” conditional compilation symbol in the WinRT library.

public class MyClass
{
    public void LoadHtml(String url, Action<HttpResponse> completed)
    {
        Http.Get(url, completed);
    }

#if WINRT
    public Task<HttpResponse> LoadHtmlAsync(string url)
    {
        return TaskHelper.RunCallbackMethod<HttpResponse, string>(LoadHtml, url);
    }
#endif
}

Exceptions and cancellation

The Task object also supports exceptions and cancellation which should be used instead of an exception property or canceled flag in the result object. The final LoadHtmlAsync method looks as follows:

public static async Task<HttpResponse> LoadHtmlAsync(string url)
{
    var task = new TaskCompletionSource<HttpResponse>();
    LoadHtml(url, result =>
    {
        if (result.Successful)
            task.SetResult(result);
        else if (result.Canceled)
            task.SetCanceled();
        else
            task.SetException(result.Exception);
    });
    return task.Task;
}

The method can be used like this:

try
{
    var response = await LoadHtmlAsync("http://www.domain.com");
}
catch (OperationCanceledException e)
{
    // TODO add your cancellation logic
}
catch (Exception e)
{
    // TODO add your exception handling logic
}

And here is the code to use the “legacy” version of the method with callbacks:

var response = LoadHtml(url, response => 
{
    if (response.Successful)
    {
        // TODO process html response
    }
    else if (response.Canceled)
    {
        // TODO add your cancellation logic
    }
    else
    {
        var exception = response.Exception;
        // TODO add your exception handling logic
    }
});
Tweet about this on TwitterShare on FacebookEmail this to someoneShare on TumblrShare on LinkedIn

Tags: , , , ,

4 responses to “Create Task-based method from “legacy” callback method”

  1. Nitha says:

    Hi,
    It seems the helper class for post method is in complete. Ho we can add credentials, post parameters, headers etc along with the request. I didn’t found any method to attach this information. Please provide more information about this.

  2. Stezma says:

    Can you provide some samples for “GET” and “POST” methods.
    For GET : How i can add arguments to my GET method.

    FOR POST : How i can add headers, to post request.also how i can post an image using this helper class.

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax