Showing posts with label UI. Show all posts
Showing posts with label UI. Show all posts

UI Responsiveness With Async in C# 5.0

It's time to revise one of my earlier posts based on a new feature of C# 5.0.  In UI Responsiveness, I had shown how to use threading and delegates to keep the UI responsive while the program was busy running a CPU-bound method in the background.  C# 5.0 has introduced the new async and await keywords that will allow us to accomplish the same goal in a much simpler way.

Note: This example now requires Visual Studio 2012.

Async

Async is used as a method modifier that marks the method as being able to run asynchronously.  Adding this modifier to the method declaration is a required step and indicates to the compiler that the method is async-ready.  Next, we'll append Async to the end of the method's name to indicate to ourselves, and anyone using our code, that this is an async method.  This change of the method's name is not required but is considered a best practice.  In general, you would also have to change the return type to either Task, or Task<T>.  However, in our example, we will be using async with an event handler, and in these cases the return type must remain void.
private async void StartButtonClick(object sender, RoutedEventArgs e)
{
    // event handler code
}

Await

Await is used to indicate two things: where the calling method should wait, and what the program should be doing while the calling method is waiting.  The program will run synchronously until it gets to the await command within the async method.  At this point, it will run asynchronously.  It starts the method indicated by the await command and also returns control to the async method's caller (in our case, this caller would be the UI).  The async method will wait here until the await command finishes.  The calling function will run until it encounters an await command itself.  Once the current async method completes the await command and the calling function has hit its next await command, the async method becomes synnchronous again.
double result = await Task.Run(() => this.ProcessStuff(maxCount));

Success

Using this new feature, the length and complexity of our sample from the UI Responsiveness post has been greatly reduced.  We no longer need any delegates nor do we need to explicitly handle creating or starting any threads.

Final Code

This code below will work with the XAML file defined in the UI Responsiveness post with one change: the method name for the startButton's Click handler has been changed from StartButtonClick to StartButtonClickAsync.
namespace WpfApplication1
{
    using System;
    using System.Threading.Tasks;
    using System.Windows;

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        // This method is marked with the new async modifier and its
        // name ends with Async.
        private async void StartButtonClick(object sender,
                                            RoutedEventArgs e)
        {
            // Indicate that the program is busy with the user's input.
            this.startButton.IsEnabled = false;
            this.startButton.Content = "Working";

            // Get user input to use for a work load in our Process
            // function. Note there is no input validation here.
            int maxCount = Convert.ToInt32(this.userInput.Text);

            // Create a new Task that will run on a background thread
            // and await the results.
            double result = await Task.Run(() =>
                                          this.ProcessStuff(maxCount));

            // Display the results.
            this.resultDisplay.Text = result.ToString();

            // Indicate that the program is ready for a new input.
            this.startButton.Content = "Start";
            this.startButton.IsEnabled = true;
        }

        private double ProcessStuff(int maxCount)
        {
            double result = 0;

            for (int i = 0; i < maxCount; i++)
            {
                for (int j = 0; j < maxCount; j++)
                {
                    result += i * j;
                }
            }

            return result;
        }
    }
}

UI Responsiveness

Today we're going to look at breaking out computationally-long tasks into separate threads to keep the UI of a program responsive while background processing is going on.  Basically, we want to have one thread that focuses on handling the UI, and a separate thread for a long-running process.

Sample Program

I frequently write programs that asks the user for a bit of input, and then get bogged down processing that input for a significant amount of time.  As an example, let's use the following C#/WPF program:
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <DockPanel DockPanel.Dock="Bottom">
            <Button x:Name="startButton" DockPanel.Dock="Right" Height="40" Width="80" Click="StartButton_Click">Start</Button>
            <TextBox x:Name="userInput" />
        </DockPanel>
        <TextBox x:Name="resultDisplay">No Results</TextBox>
    </DockPanel>
</Window>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void StartButton_Click(object sender, RoutedEventArgs e)
    {
        this.ProcessStuff(Convert.ToInt32(this.userInput.Text));
    }

    private void ProcessStuff(int maxCount)
    {
        double result = 0;

        for (int i = 0; i < maxCount; i++)
        {
            for (int j = 0; j < maxCount; j++)
            {
                result += i * j;
            }
        }

        this.resultDisplay.Text = result.ToString();
    }
}

This code will accept an integer as user input. Once the 'Start' button is clicked, it will do some arbitrary math processing on this integer (larger integers increase computation time), and will then output a result.

On my computer, an input of 10,000 or less will return a result less than a second after the 'Start' button is clicked.  However, an input of 100,000 takes about a minute and during this time, the user interface is completely frozen.  The program doesn't even redraw the window because the same program thread that handles updates to the window is busy in our nested for-loop.  Until that loop completes, it can't take any time to update the display.  This is a great way to get support calls from customers thinking that your program has frozen.

Making a Separate Thread

To fix this, we need to move the for-loops into a second thread so that they don't tie down the main thread.  First, we'll have to add a new 'using' statement up at the top of our code that will give us access to the Threading namespace:
using System.Threading;

Next, we create a new thread to run the ProcessStuff function, and then we start this new thread:
private void StartButton_Click(object sender, RoutedEventArgs e)
{
    // Create a new thread for the ProcessStuff function.
    Thread t = new Thread(this.ProcessStuff);

    // Start the thread and pass in the user input as an object.
    t.Start(Convert.ToInt32(this.userInput.Text));
}

private void ProcessStuff(object maxCount)
{
    // The Thread object requires parameters to be passed as objects,
    // let's cast that back to an int.
    int max = (int)maxCount;
    double result = 0;

    for (int i = 0; i < max; i++)
    {
        for (int j = 0; j < max; j++)
        {
            result += i * j;
        }
    }

    this.resultDisplay.Text = result.ToString();
}

This code will keep the UI responsive after clicking the 'Start' button, even with a large number in the input field.  At least, it will stay responsive right up until the unhandled exception.  The last line in ProcessStuff will throw an InvalidOperationException stating that "The calling thread cannot access this object because a different thread owns it."  We moved ProcessStuff into it's own thread, and that last line is trying to modify a WPF control back in the main thread.  Modifying members that don't belong to the current thread doesn't work.

What we need is a way for the ProcessStuff thread to run code back in the main thread.  This code can then perform the update for the ProcessStuff thread.  To do this, we'll use delegates and the UI thread's Dispatcher.

Delegates

First, let's create a new method that will accept a result of type double and then display this result in our window:
private void UpdateResults(double result)
{
    this.resultDisplay.Text = result.ToString();
}

Next, we'll set up a delegate using this method.  Delegates are data types that reference a method.  Think of them as variables that hold methods instead of numbers or text.  First, we need to define a new delegate type that matches our UpdateResults method signature.  We'll add this as a class member in MainWindow:
// This needs to match the signature of ProcessStuff.
delegate void DisplayUpdater(double result);

Then, we'll use this delegate type in our ProcessStuff method:
private void ProcessStuff(object maxCount)
{
    // The Thread object requires parameters to be passed as objects,
    // let's cast that back to an int.
    int max = (int)maxCount;
    double result = 0;

    for (int i = 0; i < max; i++)
    {
        for (int j = 0; j < max; j++)
        {
            result += i * j;
        }
    }

    // Use the delegate type we defined to create a new DisplayUpdater
    // that holds a reference to the UpdateResults method.
    DisplayUpdater resultsUpdater = new DisplayUpdater(this.UpdateResults);
}

Dispatcher

Now it's time to introduce the Dispatcher.  The Dispatcher is in charge of handling the work a thread needs to perform and therefore each thread can only have a single Dispatcher.  When events are fired or layout changes are made, the Dispatcher puts each of these tasks in a queue and works its way through them all in order.  In our original program, the Dispatcher couldn't start a drawing update until the ProcessStuff method had completed.

Now that we've got ProcessStuff in its own thread, we need to add a call to the UpdateResults method in the Dispatcher's queue.  This is done with the BeginInvoke member of the Dispatcher which takes a delegate parameter.  Every WPF control contains a reference to the Dispatcher of its current thread.  So we can access the main thread's dispatcher using the control we wish to update, resultsDisplay.  Now, our updated ProcessStuff is as follows:
private void ProcessStuff(object maxCount)
{
    // The Thread object requires parameters to be passed as objects,
    // let's cast that back to an int.
    int max = (int)maxCount;
    double result = 0;

    for (int i = 0; i < max; i++)
    {
        for (int j = 0; j < max; j++)
        {
            result += i * j;
        }
    }

    // Use the delegate type we defined to create a new DisplayUpdater
    // that holds a reference to the UpdateResults method.
    DisplayUpdater resultsUpdater = new DisplayUpdater(this.UpdateResults);
    
    // Add a call to resultsUpdater / UpdateResults() to the Dispatcher for
    // our resultDisplay TextBox.
    this.resultDisplay.Dispatcher.BeginInvoke(resultsUpdater, result);
}

Success

At this point, we've moved the time-consuming for-loop into a separate thread.  Then we added a delegate reference to the UpdateResults method.  This allowed us to queue up a call to that delegate on the main thread's Dispatcher.  Now we can run the program with larger inputs and the UI will remain responsive.  The trick to UI responsiveness is to make sure that all tasks in the Dispatcher's queue are short.  That way UI update tasks will continue to be completed on time.

This responsiveness offers an immediate benefit because the user no longer thinks the program has frozen.  However, we could also take advantage of this new functionality by showing a progress in some way or allowing the user to work on other aspects of the program.

Final Code

Here is the final C# code with the addition of a busy indication once the 'Start' button has been clicked:
namespace WpfApplication1
{
    using System;
    using System.Threading;
    using System.Windows;

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        // This needs to match the signature of ProcessStuff.
        delegate void DisplayUpdater(double result);

        public MainWindow()
        {
            InitializeComponent();
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            // Create a new thread for the ProcessStuff function.
            Thread t = new Thread(this.ProcessStuff);

            // start the thread and pass in the user input as an object
            t.Start(Convert.ToInt32(this.userInput.Text));

            // Indicate that the program is busy with the current input.
            this.startButton.Content = "Working";
            this.startButton.IsEnabled = false;
        }

        private void ProcessStuff(object maxCount)
        {
            // The Thread object requires parameters to be passed as objects,
            // let's cast that back to an int.
            int max = (int)maxCount;
            double result = 0;

            for (int i = 0; i < max; i++)
            {
                for (int j = 0; j < max; j++)
                {
                    result += i * j;
                }
            }

            // Use the delegate type we defined to create a new DisplayUpdater
            // that holds a reference to the UpdateResults method.
            DisplayUpdater resultsUpdater;
            resultsUpdater = new DisplayUpdater(this.UpdateResults);
            
            // Add a call to resultsUpdater / UpdateResults() to the Dispatcher
            // for our resultDisplay TextBox.
            this.resultDisplay.Dispatcher.BeginInvoke(resultsUpdater, result);
        }

        private void UpdateResults(double result)
        {
            // Update the display with the given result.
            this.resultDisplay.Text = result.ToString();

            // Indicate that the program is ready for a new input.
            this.startButton.Content = "Start";
            this.startButton.IsEnabled = true;
        }
    }
}

Questions and comments are always welcome.