Thursday, May 3, 2012

Async III: Cancellation

The previous posts in this series talked about the basics of async and exception handling. In this post we will take a look at cancellation and the beginnings of progress reporting.

For cancellation you can make use of a CancellationToken. This is an extra parameter that can be send to an asynchronous method call, most asynchronous methods you find in the .NET framework provide an overload with cancellation. You can extend your own asynchronous methods as well to take in a CancellationToken.

For progress reporting the IProgress interface can be used. This interface defines a Report method that can be used to report progress from within an asynchronous method. Asynchronous calls again can define overloads that take in a IProgress interface.

Let's first take a look at cancellation.

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    cts = new CancellationTokenSource();

    try
    {
        await _restCaller.PublishHikeRequest(cts.Token);

        var driverIds = await _restCaller.GetDrivers(cts.Token);

        if (driverIds != null)
            await ShowDriversOnMap(driverIds, cts.Token,
                new Progress<int>(p => statusText.Text = string.Format("{0} of {1}", p, driverIds.Count)));

        var hikerIds = await _restCaller.GetHikers(cts.Token);

        if (hikerIds != null)
            await ShowHikersOnMap(hikerIds, cts.Token);

    }
    catch (OperationCanceledException exc)
    {
        statusText.Text = exc.Message;
    }
}

As you can see, I added a datamember cts of type CancellationTokenSource to my class. A CancellationTokenSource gives you a CancellationToken through its Token property. This token can be send along to all you asynchronous methods. Once the operation gets cancelled, each asynchronous method using the token will be notified of cancellation. The result of cancellation will be an OperationCanceledException that you can catch.

Cancelling an operation is as simple as calling Cancel on the CancellationTokenSource.

protected void Cancel_Click(object sender, RoutedEventArgs e)
{
    if (cts != null)
    {
        try
        {
            cts.Cancel();
        }
        catch (AggregateException exc)
        {
            exc.Handle((ex) => {
                return true;
            });
        }
    }
}

The AggregateException I catch here, is necessary since the REST calls in the RestCaller class tend to throw additional exceptions on cancellation (this only happens occasionally). I just swallow these kinds of exceptions, since they don't add any important information.

The nice thing about cancellation is that you can have it bubble down in your code. I create the CancellationToken in the top layer of my appllication, but it's send along to different methods, which again can send it to other asynchronous methods they call. For instance the GetDrivers method sends it along with its asynchronous REST call.

public async Task<List<Guid>> GetDrivers(CancellationToken token = default(CancellationToken))
{
    List<Guid> drivers = null;

    try
    {
        var client = new HttpClient();
        var request = new HttpRequestMessage();
        request.Headers.Add("Accept", MESSAGE_TYPE);
        request.RequestUri = new Uri(string.Format("{0}/Hiker/{1}/DriverIdsNearby", BASE_URL, MY_ID));
        var response = await client.SendAsync(request, token);
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadAsStringAsync();
        drivers = await JsonConvert.DeserializeObjectAsync<List<Guid>>(content);
    }
    catch (HttpRequestException exc)
    {
        var dialog = new MessageDialog(exc.Message);
        dialog.ShowAsync();
    }

    return drivers;
}


This makes it very easy to cancel something. Whatever your code is executing at the moment, it gets cancelled.

In the next post we will look at progress reporting.

No comments:

Post a Comment