Friday, May 4, 2012

Async IV: Progress Reporting

In the previous post, I already told you that progress reporting for asynchronous calls can be done by using the IProgress interface. This interface has a Report method with which you can report progress. You probably already saw me using the IProgress interface in the previous post, but now let's explain what actually happens.


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;
    }
}

The Progress class is an implementation of the IProgress interface. The integer generic type parameter I send along is the type of progress I want reported. In this case I just want an integer value, but you can even have progress reported in a type you have specially defined for this purpous. The constructor of the Progress class takes in a lambda expression for the progress reporting. In this case I just show the number of drivers already shown on the map in the text of a TextBox.

The reporting of progress itself is done in the ShowDriversOnMap method.

private async Task ShowDriversOnMap(List<Guid> driverIds, CancellationToken token = default(CancellationToken), IProgress<int> progress = default(IProgress<int>))
{
    var count = 0;
    foreach (var driverId in driverIds)
    {
        if (progress != null && count % 10 == 0)
            progress.Report(count);

        var driver = await _restCaller.GetUser(driverId, token);
        var pushpin = new Pushpin();
        pushpin.Text = "D";
        pushpin.Foreground = new SolidColorBrush(Colors.Green);
        pushpin.Tapped += async (s, args) =>
        {
            MessageDialog dialog = new MessageDialog(string.Format("This is {0}.", driver.FullName));
            await dialog.ShowAsync();
        };
        MapLayer.SetPosition(pushpin, new Location(driver.LatitudeFrom, driver.LongitudeFrom));
        MyMap.Children.Add(pushpin);

        count++;
    }
}

I report progress in steps of 10. Since the IProgress in this case takes an int generic type parameter, I just send along an integer value to the Report method.

With the techniques you've seen so far (basics, exception handling, cancellation and progress reporting), you can get started with using async. In the next posts we will go some steps further, though, we will look at what void asynchronous methods do and start using async calls from JavaScript code. For this we will also create a WinMD library, which changes some things, both for defining asynchronous methods as for using them.

But before you move on to that, let me first give you a couple of pointers to watch out for.

Using the asynchronous features is made extremely easy for you. This implies that often you use them, without really thinking about what you are actually doing (hey, you're defining a callback, remember). I have found that once you start using async, you really need to think about how your users will be using your application. This implies thinking about cancellation, so you don't keep processing unnecessary calls.

Also, at the end of the day you end up with quite some async methods in your code. Since every time you use await, you need to make your method async. And whatever calls you method, must use await and be made async as well, and so on.

No comments:

Post a Comment