Thursday, August 9, 2012

Synchronous to Asynchronous Explained

I've posted several articles about the new async and await keywords that are available in Visual Studio 2012, but I still see some people struggle with the concept. How do you make a task asynchronous? When and where do you use the async keyword?

The answer is not simple because the need to process asynchronously depends on a variety of factors. There is overhead when you create a thread. It allocates memory and creates a new synchronization context. Having a thread communicate with other threads is even more expensive and adds complexity to your code. Fortunately, the teams at Microsoft introduced the Task Parallel Library to help simplify how you work with threads. I strongly recommend learning as much as you can about the Task object if you wish to master asynchronous programming.

There are many cases where the use of asynchronous code is straightforward, and my intent with this article is to show a very simple example to help illustrate how to create an asynchronous task and then use the new keywords to interact with it. The example is a very trivial program that computes prime numbers between 2 and 999999. You can follow along and build this in about 5 minutes.

First, create a new WPF application from Visual Studio 2012 that targets the .NET Framework 4.5. Add a simple listbox to MainWindow.xaml so the entire XAML looks like this:

<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">
    <Grid>
        <ListBox x:Name="PrimeNumbers"/>
    </Grid>
</Window>

Now add a new class and call it PrimeChecker. This is a static class that will check to see if a number is a prime number. The algorithm we'll use is a brute force algorithm that divides numbers until it finds a match:

public static class PrimeChecker
{
    public static bool IsPrime(int x)
    {
        if (x < 2) return false;
        if (x == 2) return true;
        for(var y = 2; y < x; y++)
        {
            if (x % y == 0)
            {
                return false;
            }
        }
        return true;
    }
}

In the code-behind for the MainWindow, declare an observable collection to hold the prime numbers:

private ObservableCollection<int> _primeNumbers =
    new ObservableCollection<int>();

Hook into the Loaded event for the page:

public MainWindow()
{
    InitializeComponent();
    Loaded += MainWindow_Loaded;            
}

Once the page is loaded, you will assign the observable collection to the source for the list box control, then iterate through numbers and call the checker to only add those numbers that are prime:

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    PrimeNumbers.ItemsSource = _primeNumbers;
    for(var x = 1; x < 999999; x++)
    {
        if (PrimeChecker.IsPrime(x))
        {
            _primeNumbers.Add(x);
        }
    }
}

You can now compile and run the program. If you set a breakpoint in the loop, you will see that prime numbers are being computed and added to the collection. However, the application itself will freeze and you won't see anything. This is because all of the computations are happening on the same thread, which happens to be the main UI thread. This will freeze that thread and prevent the system from updating the display and accepting user input. This is obviously not the desired effect. What would make more sense is to do the heavy lifting, or the brute force algorithm to determine if a number is prime, on a separate thread that does not block the UI. This is easily done in a few steps. First, add a new method to the PrimeChecker class. Note I'm following the standard convention of marking an asynchronous method with the Async suffix:

public static Task<bool> IsPrimeAsync(int x)
{
    return Task.Run(() => IsPrime(x));
}

That's it. That's the only code needed to take the computation and run it on a separate thread. The Task takes care of it. Notice that the return is typed to the actual value we want to inspect (boolean) but wrapped in a Task object. Now that you have the work being performed asynchronously, how do you interact with the result? This takes two simple steps.

First, declare your intent to wait for an asynchronous task by prefixing the method with the async keyword. We are still going to use the MainWindow_Loaded method, so it will now look like this:

async void MainWindow_Loaded(object sender, RoutedEventArgs e)

Next, change the call to check for a prime number to use the asynchronous method, and then simply wait for it. The await keyword specifies that the task should run and return a result, but the thread should not block while waiting. It is as simple as changing the line to this:

if (await PrimeChecker.IsPrimeAsync(x))

And that's the beautify of the new keywords. Without those keywords, you would need to explicitly wait for the thread, either by using a helper method on the Task or by using events and registering for a completion. This would fragment your code and if you had a lot of asynchronous tasks could render it very difficult to read and maintain. The new keywords make it easy to both create asynchronous methods and consume them inline without blocking. The entire method now looks like this:

async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    PrimeNumbers.ItemsSource = _primeNumbers;
    for(var x = 1; x < 999999; x++)
    {
        if (await PrimeChecker.IsPrimeAsync(x))
        {
            _primeNumbers.Add(x);
        }
    }
}
When you run the application this time, you'll see the list quickly start to fill with the list of prime numbers. You can scroll the list while it is computing because the main thread is no longer frozen by the brute force computations.

Jeremy Likness

3 comments:

  1. ...and usable overview. After the threading-invoking nightmare in .NET 1.1 (and more-or-less in 2.0-3.x) it's always good to read about new solutions.

    ReplyDelete
  2. Good example.

    Additional note: if you exit the application, that asynchronous loop gets killed silently.

    ReplyDelete