Pseudorandom Knowledge

UI threading in .NET

Here is a simple WPF program with nine buttons. Each button is supposed to run the code below, which takes a couple of seconds, and report when it's done.

private void PerformCalculation()
{
    // Placeholder calculation
    for (int i = int.MinValue; i < int.MaxValue; i++) ;
}

Try to work out how each button will behave before clicking the blurred description to reveal it. Will the interface remain repsonsive? Will an exception be raised? Will the program crash?

Button 1

private void Button1_Click(object sender, RoutedEventArgs e)
{
    PerformCalculation();
    TextBox.AppendText("Done");
}

Behaviour: The interface will be unresponsive while the calculation is being performed. Only the UI thread may update the interface and it is blocked from doing so while it executes the method. Once Done has been written to text box the interface will become responsive again.

Button 2

private async void Button2_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() => PerformCalculation());
    TextBox.AppendText("Done");
}

Behaviour: The interface will remain responsive while the calculation is being performed. The work will be done on a worker thread, leaving the UI thread free to update the interface. When the task is finished the UI thread will regain control and write Done to the text box.

Button 3

private async void Button3_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() =>
    {
        PerformCalculation();
        TextBox.AppendText("Done");
    });
}

Behaviour: The interface will remain responsive while the calculation is being performed. Then the program will crash due to an unhandled InvalidOperationException. This is because the program attempts to update the text box from a worker thread, which it is not allowed to do.

Button 4

private void Button4_Click(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        PerformCalculation();
        TextBox.AppendText("Done");
    });
}

Behaviour: This button will appear to do nothing at all. An exception will be thrown when the program attempts to update the text box from a worker thread. However, when an unhandled exception occur within a task it will be saved until the task is joined with the rest of the code. Since that never happens here the exception is silently ignored.

Button 5

private async void Button5_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() => PerformCalculation()).ConfigureAwait(false);
    TextBox.AppendText("Done");
}

Behaviour: By using ConfigureAwait(false) we tell the program to continue execution on the worker thread after the calculation has been performed. Since the text box cannot be updated from a worker thread the program crashes due to an unhandled exception.

Button 6

private async void Button6_Click(object sender, RoutedEventArgs e)
{
    var syncContext = SynchronizationContext.Current;
    await Task.Run(() =>
    {
        PerformCalculation();
        syncContext.Post(_ => TextBox.AppendText("Done"), null);
    });
}

Behaviour: This code works just fine. By capturing the synchronization context of the UI thread we can post back to it from the worker thread. The text box fill be written to from the UI thread.

Button 7

private async void Button7_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() =>
    {
        PerformCalculation();
        Dispatcher.Invoke(() => TextBox.AppendText("Done"));
    });
}

Behaviour: This code also works fine. Dispatcher is a property of DispatcherObject which Window penultimately derives from. When invoking an action on this dispatcher it will be executed on the UI thread.

Button 8

private async void Button8_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() => PerformCalculation());
    MessageBox.Show("Done");
}

Behaviour: A dialog box with the message Done will be shown after the calculation has been performed. The dialog is modal, it must be closed before the rest of the interface can be interacted with.

Button 9

private async void Button9_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() =>
    {
        PerformCalculation();
        MessageBox.Show("Done");
    });
}

Behaviour: This will also show a dialog box after the calculation is done. Dialog boxes can be opened from worker threads. However, there is one significant difference. This dialog box is not modal. It does not have to be closed before the rest of the interface can be interacted with.