TaskCompletionSource in real life (part 2 of 2)

This is the second of two parts. The first one is here: TaskCompletionSource in real life (part 1 of 2)

Yesterday I showed an example of how you can use TaskCompletionSource to mock calls that returns a Task for testing purposes. In the code samples I used an interface called IAsyncWebClient. This was of course no coincidence. WebClient happens to be a good candidate for showing the other major case where I use TaskCompletionSource.

Wrapping async operations that do not follow TPL-supported patterns

It should be no news that asynchronous programming in C# these days revolve around Task objects. Also, it should be no news that asynchronicity is not necessarily the same as starting a new thread for the work. Right from the beginning of .NET you had access to reading and writing streams asynchronously using the BeginRead/EndRead method pairs, along with an IAsyncResult. This would use a mechanism known as I/O completion ports which uses a pool of pre-allocated threads, making it way more efficient than creating new threads for such requests.

The Task Parallel Library supports this pattern through the TaskFactory.FromAsync method, which essentially lets you convert this asynchronous pattern into a Task. Unfortunately there are types in the Base Class Library that has asynchronous functionality but that do not follow this pattern and one of them is the WebClient class.

The pattern used in the WebClient class is that you subscribe to a Completed-event, then you invoke the method that will start the download, and finally the event will be raised when the operation completes (either because it was successful, cancelled or failed).

In yesterday's blog post I used the following interface, invented for the demo code in the article:

public interface IAsyncWebClient
{
    Task GetStringAsync(Uri uri);
}

I want to use this interface to create a type that wraps a WebClient. For natural reasons, this is a tough one to make TDD style, simply because we have external dependencies that are not (easily) mocked (such as a server from which the test can download a document) so this time I will break my habit of presenting a test followed by the implementation. I will simply skip the test part and present the code, with lots of comments in it:

public class AsyncWebClient : IAsyncWebClient
{
    public Task GetStringAsync(Uri uri)
    {
        // create completion source
        var tcs = new TaskCompletionSource();

        // create a web client for downloading the string
        var wc = new WebClient();

        // Set up variable for referencing anonymous event handler method. We
        // need to first assign null, and then create the method so that we
        // can reference the variable within the method itself.
        DownloadStringCompletedEventHandler downloadCompletedHandler = null;

        // Set up an anonymous method that will handle the DownloadStringCompleted
        // event.
        downloadCompletedHandler = (s, e) =>
        {
            // Unsubscribe the event listener (to allow the WebClient to
            // be garbage collected).
            wc.DownloadStringCompleted -= downloadCompletedHandler;
            if (e.Error != null)
            {
                // If the download failed, set the error on the task completion source
                tcs.TrySetException(e.Error);
            }
            else if (e.Cancelled)
            {
                // If the download was cancelled, signal cancellation to the
                // task completion source.
                tcs.TrySetCanceled();
            }
            else
            {
                // If the download was successful, set the result on the task completion
                // source
                tcs.TrySetResult(e.Result);
            }

            wc.Dispose();
        };

        // Subscribe to the completed event
        wc.DownloadStringCompleted += downloadCompletedHandler;

        // Start the asynchronous download
        wc.DownloadStringAsync(uri);

        // Return the Task object from the TaskCompletionSource. This object will be monitored
        // by the calling code for the result, that is provided by the TrySetXxxx calls above.
        return tcs.Task;
    }
}

This way we can use the TaskCompletionSource to provide a Task-based wrapper around a type that provide asynchronous methods, but that deviate from the patterns that are directly supported by the Task Parallel Library.

I have posted this code at my BitBucket repository, slightly extended to also contain cancellation support and a simple winforms client (yes, winforms, I felt a bit retro and lazy).