Thursday, May 17, 2012

Async V: void methods

In the previous post on async, I told you, we would go a bit deeper into void methods, since they behave differently than methods returning a Task or Task<t>. The difference is that, where non-void returning methods give you callback behavior, void methods give you fire and forget behavior. This can give you confusing results if you don't know how to spot and use them.

Let's take a look again at our OnNavigatedTo method.

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    await _restCaller.PublishHikeRequest();

    var driverIds = await _restCaller.GetDrivers();

    if (driverIds != null) 
        await ShowDriversOnMap(driverIds);

    var hikerIds = await _restCaller.GetHikers();

    if (hikerIds != null) 
        await ShowHikersOnMap(hikerIds);
}

Now, the original version of this code looked a bit different, I actually altered it to get the entire explanation on async right for you. The original version was actually written as follows:

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    await _restCaller.PublishHikeRequest();

    var driverIds = await _restCaller.GetDrivers();

    if (driverIds != null) 
        ShowDriversOnMap(driverIds);

    var hikerIds = await _restCaller.GetHikers();

    if (hikerIds != null) 
        ShowHikersOnMap(hikerIds);
}

The difference here is that I didn't use await on the calls to ShowDriversOnMap and ShowHikersOnMap. The reason for this is that while writing this code, I had made these two methods void. You are not allowed to use the await keyword on a void method.

private async void ShowDriversOnMap(List<Guid> driverIds)
{
    foreach (var driverId in driverIds)
    {
        var driver = await _restCaller.GetUser(driverId);
        var pushpin = new Pushpin();
        pushpin.Text = "D";
        pushpin.Tapped += async (s, args) =>
        {
            MessageDialog dialog = 
                new MessageDialog(string.Format("This is {0}.", driver.FullName));
            await dialog.ShowAsync();
            MessageDialog secondDialog = 
                new MessageDialog("I am shown only after the previous dialog finishes.");
            await secondDialog.ShowAsync();
        };

        MapLayer.SetPosition(pushpin, 
            new Location(driver.LatitudeFrom, driver.LongitudeFrom));
        MyMap.Children.Add(pushpin);
    }
}

The usage of void for these methods makes very much sense to me. You are just showing something on a UI, no need for these two methods to return anything, right? Well, indeed, right, but what I didn't expect was that the behavior of these void methods was completely different from the Task returning methods. That is, void methods behave as fire and forget methods. You can't even use await on them, that is why those keywords when calling these methods are missing, not because I forgot them, but because you're not even allowed to use them. Your compiler will complain if you do.

The behavior you get in this case, is that, while the drivers are being shown on the map, since this call is fire and forget, the processing already continues with fetching all the hikers and showing those on the map as well. So you actually get parallel behavior and not callback behavior. In the case of showing the drivers and hikers on the map simultaneously, this might not seem such a big of a problem, but I can imagine cases in which it does present a problem.

What is even a bigger problem is that for void methods you don't even have a clue that you are calling an async method. With Task and Task<T> returning methods you get a nice tooltip that says you are using an awaitable.


With void returning async methods, you get none of this. The tooltip just says it's a void method, you have no clue of the fire and forget behavior.


That is why, as far as async goes, it is recommended you always use Task or Task<T> returning methods, these ones give you callback behavior. The void returning methods are recommended for use only on event handlers.

If using Task returning methods, you can also get parallel behavior. For this, you can use the WhenAll method on the Task class.


protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    await _restCaller.PublishHikeRequest();

    var driverIds = await _restCaller.GetDrivers();

    var hikerIds = await _restCaller.GetHikers();

    var showDriversTask = ShowDriversOnMap(driverIds);

    var showHikersTask = ShowHikersOnMap(hikerIds);

    Task.WhenAll(showDriversTask, showHikersTask);
}

Both tasks will be executed in parallel and execution of the rest of the method will continue when both tasks are finished. Another handy method on the Task class is the WhenAny method. It behaves more or less the same as WhenAll, but it continues with the rest of the method body as soon as one of tasks you give it as a parameter finishes.

Next post we will look at using async methods from JavaScript in WinRT.

No comments:

Post a Comment