Async-Await and the UI thread

frozen-computerA few weeks ago I shared my experience of moving from web development to mobile development. In this article I mentioned asynchronous programming which is mandatory for desktop and mobile applications.

Today I will give you an example to demonstrate how to use the async-await pattern in order to avoid having a User Interface (UI) that freezes (does not respond to user interaction).

The synchronous approach

In this demo, I have created a simple WPF application with a button and a label, I will not use the Model-View-ViewModel (MVVM) pattern in this example. When clicking on the button some data will be retrieved on a repository and processed by a service before updating the label with the value.

main-window

Here is the XAML for this window:

<Window x:Class="AsyncAwait.WPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AsyncAwait.WPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="497" Click="button_Click"/>
        <Label x:Name="label" Content="Label" HorizontalAlignment="Left" Margin="10,45,0,0" VerticalAlignment="Top" Width="497"/>
    </Grid>
</Window>

And the C# code can be found below.

// MainWindow.xaml.cs
public partial class MainWindow : Window
{
    public MainWindow()
    {
        _domainService = new DomainService();
        InitializeComponent();
    }
 
    private readonly DomainService _domainService;
 
    private void button_Click(object sender, RoutedEventArgs e)
    {
        var labelData = _domainService.GetData();
        this.label.Content = labelData;
    }
}
 
// DomainService.cs
public class DomainService
{
    public DomainService()
    {
        _dataRepository = new DataRepository();
    }
 
    private readonly DataRepository _dataRepository;
 
    public string GetData()
    {
        var data = _dataRepository.RetrieveData();
        data = ComputeData(data);
        return data;
    }
 
    private string ComputeData(string data)
    {
        Thread.Sleep(5000); // mocking calculation latency
        return data.ToUpper();
    }
}
 
// DataRepository.cs
public class DataRepository
{
    public string RetrieveData()
    {
        return RetrieveDataInternal();
    }
 
    private string RetrieveDataInternal()
    {
        Thread.Sleep(5000); // mocking latency
        return "my data";
    }
}

I used the Thread.Sleep() method to mock latency, due to database/network access and calculation time.

From a syntax point of view the code compile and does what it needs to do. But when we launch this program, the UI will freeze for 10 seconds and will not respond to the user interactions. This clearly not the best experience for a software.

The application has this behavior because all the work is done on the UI thread and it won’t be able to do its main work: displaying and refreshing the UI (responding to the user inputs is part of it).

It is time to update the code to make the code asynchronous.

Using Async-Await

I refactored the previous code to be able to use the async and await keywords.

// MainWindow.xaml.cs
public partial class MainWindow : Window
{
    public MainWindow()
    {
        _domainService = new DomainService();
        InitializeComponent();
    }
 
    private readonly DomainService _domainService;
 
    private async void button_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            var labelData = await _domainService.GetDataAsync();
            this.label.Content = labelData;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}
 
// DomainService.cs
public class DomainService
{
    public DomainService()
    {
        _dataRepository = new DataRepository();
    }
 
    private readonly DataRepository _dataRepository;
 
    public async Task<string> GetDataAsync()
    {
        var data = await _dataRepository.RetrieveDataAsync();
        data = ComputeData(data);
        return data;
    }
 
    private string ComputeData(string data)
    {
        Thread.Sleep(5000); // mocking calculation latency
        return data.ToUpper();
    }
}
 
// DataRepository.cs
public class DataRepository
{
    public Task<string> RetrieveDataAsync()
    {
        return RetrieveDataInternalAsync();
    }
 
    private Task<string> RetrieveDataInternalAsync()
    {
        Thread.Sleep(5000);
        return Task.FromResult("my data");
    }
}

Great! It should be better this way, let’s try it… It still freeze for 10 seconds… Even with the async-await pattern!

That is normal, the program does not create a Task on a separate thread and therefore all the code is still executed on the UI thread. Only using the async/await keywords is not enough to make the code asynchronous. Some methods of the .NET framework will create tasks on separate thread to allow you to use this pattern, like the GetAsync() method of the HttpClient class (see more examples here). You can detect them by looking at the “Async” suffix in the method name, it is a convention to use this suffix to notify calling code that the method is “awaitable”.

In my case I will fix my problem by creating a new Task with the Task.Run() method:

// DataRepository.cs
private Task<string> RetrieveDataInternalAsync()
{
    return Task.Run(() =>
    {
        Thread.Sleep(5000); // mocking latency
        return "my data";
    });
}

This gives me a Task I can use in my class hierarchy to run the code asynchronously. Now if we run the program it is better but the UI will still freeze for about 5 seconds…

This is caused by the ComputeData() method in the DomainService class which is executed on the UI thread. Well, I can use another Task.Run() to solve this problem. True but I can also use another feature of the Task to solve my issue: Task.ConfigureAwait():

// DomainService.cs
public async Task<string> GetDataAsync()
{
    var data = await _dataRepository.RetrieveDataAsync().ConfigureAwait(false);
    data = ComputeData(data);
    return data;
}

The parameter of the ConfigureAwait method (named “continueOnCaptureContext”) allows us to specify if we want that the rest of the method to executed on the captured context or not. This refers to the calling thread which is the UI thread in my case. By default this value is set to true and that means that the ComputeData() method was executed on the UI thread which explains why the application froze.

Now, if we run the application, the label will still be updated after 10 seconds but the Window will be responding to the user interactions which is, in my opinion way better for the user experience.

This ConfigureAwait tip looks great, let me use it on the button_Click() method!

// MainWindow.xaml.cs
private async void button_Click(object sender, RoutedEventArgs e)
{
    try
    {
        var labelData = await _domainService.GetDataAsync().ConfigureAwait(false);
        this.label.Content = labelData;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

But when I run the program, I have the following result:

main-window-failed

I have an InvalidOperationException with the following message: “The calling thread cannot access this object because a different thread owns it”, displayed in a message box (in French in my case due to my configuration).

This error happens because I try to access the label property of the MainWindow from a thread different from the UI one. This is caused by the ConfigureAwait(false) I put, in this case I don’t want it because the code needs to be executed on the captured context.

Conclusion

Working with the async/await pattern needs practice to be understood, I wrote this article to explains some concepts of it by showing its impacts on a UI interface. I do not claim to be an expert on the subject, there is a lot more I need to learn on this topic. But remember that you need a Task to work with to unlock the potential of this pattern.

The Task.ConfigureAwait() method is also a powerful ally to lighten the work done by the UI thread but it needs to be used carefully to avoid invalid operations in the application.

The async keywork can only be used on methods returning a Task, a Task<T>, or void. This last case can be dangerous and should be avoided as much as possible, in my case the only async void method is the button_click event handler. I took care of encapsulating the whole code in a try/catch block and every time you see an async void method you should do it as well. Because it behaves as a “fire and forget” call and if you do not handle the exceptions in the method, they will be raise later without knowing exactly when or where and it will likely stop the process and the application.

I hope this example can help you if you are starting with asynchronous programming in C# .NET.

See you next time!


Image credits:

https://www.flickr.com/photos/netbooknews/

 

2 thoughts on “Async-Await and the UI thread

  1. Salut Julien,

    Super article au passage.

    Je me permets de te poser une question en français parce que j’ai beaucoup de mal à faire fonctionner ce pattern depuis 5 à 8 jours.
    Je développe une application assez lourde en WPF/MVC MVVM Light.
    J’aurais voulu paralléliser le chargement de l’application pour plus de rapidité mais le problème de la mise jour UI me bloque.

    J’ai un MainViewModel contenant plusieurs DataViewModel lui même contenant plusieurs ViewModelX.
    Chaque ViewModelX contient une vue associée comme le recommande le pattern MVVM. Je n’arrive pas à initialiser les DataViewModel en parallèle via des task.Run puisque chaque DataViewModel veut construire plusieurs ViewModelX qui eux-même veulent construire des ViewX. ViewX est à chaque fois un UserControl. VS2012 plante parce que soit c’est le thread UI qui doit construire un UserControl, soit c’est un thread secondaire qui veut accéder à un élément du thread UI…

    Bref je suis perdu, je peux t’envoyer une version simplifiée qui pose déjà problème si tu veux. Je peux aussi traduire tout ce post pour les lecteurs anglophones.

    Peut-on réellement charger une UI lourde en parallèle (<List<List<List>>…) puisque c’est le thread UI qui au final se charge de l’affichage ? Ajouter des Dispatcher.invoke ne me plait pas cela fait revenir tout le travail d’initialisation au même pour moi (thread UI). je précise que j’ai fais tout ce que je pouvais en asynchrone async/await (appels wcf etc…quelques secondes gagnées ! :)), mais le plus lourd reste LARGEMENT l’initialisation : 1 MainViewModel avec 4 DataViewModel avec chacun 7 ViewModelX/ViewX = 25 secondes.

    Merci de de ton retour

    Like

  2. Using Thead.Sleep is a really bad practice for testing asynchronous code. It suspends the current thread – the UI thread in your case – for a specified time. So yes, your application is frozen but it’s mainly because of this line of code. . When you call a remote service in a sync way, it’s less visible. If you want to call a real remote service, there are many online web api for testing purposes.

    Task.Run is a way to do “async over sync” but it’s not always to recommended to use it. It will take a new thread from .net the thread pool and yes, it’s a new thread not a task. Task.Run could be useful to improve responsiveness but it’s fairly better to have async all the way, and call a rest api via async methods, because it will use another kind of thread related to IO. Putting Task.Run everywhere is not a way to do async properly.

    Task.Delay is better than Task.Run { Thread.Sleep(XXX) }

    Last thing, this problem is also possible for asp.ne : the worker process could be deadlocked with a bad use of async/await because asp.net has another synchronization context model.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s