Moving from callbacks to await & async in Silverlight

This post was written by Ben Clark.

In Indico we make liberal use of asynchronous data calls to fetch large amounts of data whilst keeping the user interface responsive. Each component can make an asynchronous request to the server and update itself when (or sometimes if!) the data is returned.

Up until now we've been achieving this using Callbacks. Put simply we start an asynchronous request running with a link to a method to run once the data is returned. This generally works pretty well; It stops the user interface locking up and is fairly easy to implement, though understanding program flow can get complex, especially when multiple levels of callback are used.

As Microsoft puts it;

You can avoid performance bottlenecks and enhance the overall responsiveness of your application by using asynchronous programming. However, traditional techniques for writing asynchronous applications can be complicated, making them difficult to write, debug, and maintain.

All of this changed with the release of the Async and Await system in .NET 4.5, promising developers the ability to write asynchronous code as if it were linear with the compiler working out all of the complex stuff in the background. After some discussion we decided to dive head first into the new system and see how it fared.

Firstly, async/await isn't supported out of the box on Silverlight (WPF gets all the best stuff..), but luckily there is a NuGet package to bring this functionality. The package is called Async for .NET Framework 4, Silverlight 4 and 5, and Windows Phone 7.5 and 8. Once this was installed we were ready to go.

Here's a (very) simplified version of our data agent system using callbacks. We're using the MVVM pattern with a View Model and Data Agent.

In Component View Model:

public override void PerformLoad()

{
    //provide GetChartData with a callback to SetChartData
    DataAgent.GetChartData(param1, param2, ..., SetChartData);

}

public override void SetChartData(ObservableCollection<TEntity> items)

{

    Data = items;

}

In Data Agent:

//Set load completed handler

Webclient.OpenReadAsyncCompleted += LoadCompleted;

//GetChartData accepts a callback
protected void GetChartData(Action<ObservableCollection<TEntity>> callback)

{
    //...Cancel any previous requests

    //...save callback somewhere


    //begin reading data
    WebClient.OpenReadAsync(url);
}

//WebClient Data load completed event
protected virtual void LoadCompleted(object sender, OpenReadAsyncCompltedEventArgs e)

{
    //...handle errors

    //...deserialize

    //callback to View Model
    callback(data);
}

In this simple example it's fairly easy to follow program control, but we're still having to pass callbacks around the place. For more complex examples it can become very confusing working out the program flow and which parts are asynchronous.

Now here's how we implement the same system using async and await.

In Component View Model:

//notice async keyword
async public override void PerformLoadAsync()

{
    //Looks synchronous to me! call SetChartData with GetChartData (await its arrival)
    SetChartData(await DataAgent.GetChartData(param1, param2, ...));
}

//Same as previous
public override void SetChartData(ObservableCollection<TEntity> items)
{
    Data = items;
}

Note: To make things even simpler we could remove SetChartData and simply do;
Data = await DataAgent.GetChartData(param1, param2, ...);

In Data Agent:

//No WebClient Load Completed handler needed
//This now returns a Task, which await unwraps for us automatically, notice no event args
async protected Task<ObservableCollection<TEntity>> GetChartDataAsync()
{
    //...Cancel any previous requests

    //No nasty WebClient load completed handler, just await the data
    return LoadCompleted(await WebClient.OpenReadTaskAsync(uri));
}


//now return the result directly from LoadCompleted!
protected virtual ObservableCollection<TEntity> LoadCompleted(Stream s)
{
    //...deserialize

    return data;
}

After a little bit of head scratching (it's a little hard to let go of the asynchronous thinking initially!) we've found using async/await is much simpler, much cleaner and considerably easier to understand. It's great to be able to simply await data and let the compiler do all of the heavy lifting. Asynchronous data is wrapped in a Task<>, which is automatically unwrapped by using the await keyword. It really couldn't be much simpler!

One final point is Microsoft suggests adding Async to the name of any async methods - So in this case, GetChartData() becomes GetChartDataAsync() - this really helps to hint to other developers that we're using Async.