13 min read

(For more resources related to this topic, see here.)

Creating an async method

The TAP is a new pattern for asynchronous programming in .NET Framework 4.5. It is based on a task, but in this case a task doesn’t represent work which will be performed on another thread. In this case, a task is used to represent arbitrary asynchronous operations.

Let’s start learning how async and await work by creating a Windows Presentation Foundation (WPF ) application that accesses the web using HttpClient. This kind of network access is ideal for seeing TAP in action. The application will get the contents of a classic book from the web, and will provide a count of the number of words in the book.

How to do it…

Let’s go to Visual Studio 2012 and see how to use the async and await keywords to maintain a responsive UI by doing the web communications asynchronously.

  1. Start a new project using the WPF Application project template and assign WordCountAsync as Solution name .
  2. Begin by opening MainWindow.xaml and adding the following XAML to create a simple user interface containing Button and TextBlock:

    <Window x_Class="WordCountAsync.MainWindow" Title="WordCountAsync" Height="350" Width="525"> <Grid> <Button x_Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="219,195,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="-0.2,0.45" Click="StartButton_Click"/> <TextBlock x_Name="TextResults" HorizontalAlignment="Left" Margin="60,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="139" Width="411"/> </Grid> </Window>

  3. Next, open up MainWindow.xaml.cs. Go to the Project and add a reference to System.Net.Http.
  4. Add the following using directives to the top of your MainWindow class:

    using System; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Windows;

  5. At the top of the MainWindow class, add a character array constant that will be used to split the contents of the book into a word array.

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };

  6. Add a button click event for the StartButton and add the async modifier to the method signature to indicate that this will be a async method. Please note that async methods that return void are normally only used for event handlers, and should be avoided.

    private async void StartButton_Click(object sender, RoutedEventArgs e) { }

  7. Next, let’s create a async method called GetWordCountAsync that returns Task<int>. This method will create HttpClient and call its GetStringAsync method to download the book contents as a string. It will then use the Split method to split the string into a wordArray. We can return the count of the wordArray as our return value.

    public async Task<int> GetWordCountAsync() { TextResults.Text += "Getting the word count for Origin of Species...\n"; var client = new HttpClient(); var bookContents = await client.GetStringAsync
    (@"http://www.gutenberg.org/files/2009/2009.txt"); var wordArray = bookContents.Split
    (delimiters, StringSplitOptions.RemoveEmptyEntries); return wordArray.Count(); }

  8. Finally, let’s complete the implementation of our button click event. The Click event handler will just call GetWordCountAsync with the await keyword and display the results to TextBlock.

    private async void StartButton_Click(object sender, RoutedEventArgs e) { var result = await GetWordCountAsync(); TextResults.Text += String.Format
    ("Origin of Species word count: {0}",result); }

  9. In Visual Studio 2012, press F5 to run the project. Click on the Start button, and your application should appear as shown in the following screenshot:

How it works…

In the TAP, asynchronous methods are marked with an async modifier. The async modifier on a method does not mean that the method will be scheduled to run asynchronously on a worker thread. It means that the method contains control flow that involves waiting for the result of an asynchronous operation, and will be rewritten by the compiler to ensure that the asynchronous operation can resume this method at the right spot.

Let me try to put this a little more simply. When you add the async modifier to a method, it indicates that the method will wait on an asynchronous code to complete. This is done with the await keyword. The compiler actually takes the code that follows the await keyword in an async method and turns it into a continuation that will run after the result of the async operation is available. In the meantime, the method is suspended, and control returns to the method’s caller.

If you add the async modifier to a method, and then don’t await anything, it won’t cause an error. The method will simply run synchronously.

An async method can have one of the three return types: void, Task, or Task<TResult>. As mentioned before, a task in this context doesn’t mean that this is something that will execute on a separate thread. In this case, task is just a container for the asynchronous work, and in the case of Task<TResult>, it is a promise that a result value of type TResult will show up after the asynchronous operation completes.

In our application, we use the async keyword to mark the button click event handler as asynchronous, and then we wait for the GetWordCountAsync method to complete by using the wait keyword.

private async void StartButton_Click(object sender, RoutedEventArgs e) { StartButton.Enabled = false; var result = await GetWordCountAsync(); TextResults.Text += String.Format("Origin of Species word count: {0}", .................. result); StartButton.Enabled = true; }

The code that follows the await keyword, in this case, the same line of code that updates TextBlock, is turned by the compiler into a continuation that will run after the integer result is available.

If the Click event is fired again while this asynchronous task is in progress, another asynchronous task is created and awaited. To prevent this, it is a common practice to disable the button that is clicked.

It is a convention to name an asynchronous method with an Async postfix, as we have done with GetWordCountAsync.

Handling Exceptions in asynchronous code

So how would you add Exception handling to code that is executed asynchronously? In previous asynchronous patterns, this was very difficult to achieve. In C# 5.0 it is much more straightforward because you just have to wrap the asynchronous function call with a standard try/catch block.

On the surface this sounds easy, and it is, but there is more going on behind the scene that will be explained right after we build our next example application.

For this recipe, we will return to our classic books word count scenario, and we will be handling an Exception thrown by HttpClient when it tries to get the book contents using an incorrect URL.

How to do it…

Let’s build another WPF application and take a look at how to handle Exceptions when something goes wrong in one of our asynchronous methods.

  1. Start a new project using the WPF Application project template and assign AsyncExceptions as Solution name .
  2. Begin by opening MainWindow.xaml and adding the following XAML to create a simple user interface containing Button and a TextBlock:

    <Window x_Class="WordCountAsync.MainWindow" Title="WordCountAsync" Height="350" Width="525"> <Grid> <Button x_Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="219,195,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="-0.2,0.45" Click="StartButton_Click"/> <TextBlock x_Name="TextResults" HorizontalAlignment="Left" Margin="60,28,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="139" Width="411"/> </Grid> </Window>

  3. Next, open up MainWindow.xaml.cs. Go to the Project Explorer , right-click on References , click on Framework from the menu on the left side of the Reference Manager , and then add a reference to System.Net.Http.
  4. Add the following using directives to the top of your MainWindow class:

    using System; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Windows;

  5. At the top of the MainWindow class, add a character array constant that will be used to split the contents of the book into a word array.

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };

  6. Now let’s create our GetWordCountAsync method. This method will be very similar to the last recipe, but it will be trying to access the book on an incorrect URL. The asynchronous code will be wrapped in a try/catch block to handle Exception. We will also use a finally block to dispose of HttpClient.

    public async Task<int> GetWordCountAsync() { ResultsTextBlock.Text += "Getting the word count for Origin of Species...\n"; var client = new HttpClient(); try { var bookContents = await client.GetStringAsync
    (@"http://www.gutenberg.org/files/2009/No_Book_Here.txt"); var wordArray = bookContents.Split
    (delimiters, StringSplitOptions.RemoveEmptyEntries); return wordArray.Count(); } catch (Exception ex) { ResultsTextBlock.Text += String.Format("An error has occurred:
    {0} \n", ex.Message); return 0; } finally { client.Dispose(); } }

  7. Finally, let create the Click event handler for our StartButton. This is pretty much the same as the last recipe, just wrapped in a try/catch block. Don’t forget to add the async modifier to the method signature.

    private async void StartButton_Click(object sender, RoutedEventArgs e) { try { var result = await GetWordCountAsync(); ResultsTextBlock.Text += String.Format
    ("Origin of Species word count: {0}", result); } catch(Exception ex) { ResultsTextBlock.Text += String.Format("An error has occurred: {0} \n", ex.Message); } }

  8. Now, in Visual Studio 2012, press F5 to run the project. Click on the Start button. Your application should appear as shown in the following screenshot:

How it works…

Wrapping your asynchronous code in a try/catch block is pretty easy. In fact, it hides some of the complex work Visual Studio 2012 to doing for us.

To understand this, you need to think about the context in which your code is running.

When the TAP is used in Windows Forms or WPF applications, there’s already a context that the code is running in, such as the message loop UI thread. When async calls are made in those applications, the awaited code goes off to do its work asynchronously and the async method exits back to its caller. In other words, the program execution returns to the message loop UI thread.

The Console applications don’t have the concept of a context. When the code hits an awaited call inside the try block, it will exit back to its caller, which in this case is Main. If there is no more code after the awaited call, the application ends without the async method ever finishing.

To alleviate this issue, Microsoft included async compatible context with the TAP that is used for Console apps or unit test apps to prevent this inconsistent behavior. This new context is called GeneralThreadAffineContext.

Do you really need to understand these context issues to handle async Exceptions? No, not really. That’s part of the beauty of the Task-based Asynchronous Pattern.

Cancelling an asynchronous operation

In .NET 4.5, asynchronous operations can be cancelled in the same way that parallel tasks can be cancelled, by passing in CancellationToken and calling the Cancel method on CancellationTokenSource.

In this recipe, we are going to create a WPF application that gets the contents of a classic book over the web and performs a word count. This time though we are going to set up a Cancel button that we can use to cancel the async operation if we don’t want to wait for it to finish.

How to do it…

Let’s create a WPF application to show how we can add cancellation to our asynchronous methods.

  1. Start a new project using the WPF Application project template and assign AsyncCancellation as Solution name .
  2. Begin by opening MainWindow.xaml and adding the following XAML to create our user interface. In this case, the UI contains TextBlock, StartButton, and CancelButton.

    <Window x_Class="AsyncCancellation.MainWindow" Title="AsyncCancellation" Height="400" Width="599"> <Grid Width="600" Height="400"> <Button x_Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="142,183,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="-0.2,0.45" Click="StartButton_Click"/> <Button x_Name="CancelButton" Content="Cancel" HorizontalAlignment="Left" Margin="379,185,0,0" VerticalAlignment="Top" Width="75" Click="CancelButton_Click"/> <TextBlock x_Name="TextResult" HorizontalAlignment="Left" Margin="27,24,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="135" Width="540"/> </Grid> </Window>

  3. Next, open up MainWindow.xaml.cs, click on the Project Explorer , and add a reference to System.Net.Http.
  4. Add the following using directives to the top of your MainWindow class:

    using System; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Windows;

  5. At the top of the MainWindow class, add a character array constant that will be used to split the contents of the book into a word array.

    char[] delimiters = { ' ', ',', '.', ';', ':', '-', '_', '/', '\u000A' };

  6. Next, let’s create the GetWordCountAsync method. This method is very similar to the method explained before. It needs to be marked as asynchronous with the async modifier and it returns Task<int>. This time however, the method takes a CancellationToken parameter. We also need to use the GetAsync method of HttpClient instead of the GetStringAsync method, because the former supports cancellation, whereas the latter does not. We will add a small delay in the method so we have time to cancel the operation before the download completes.

    public async Task<int> GetWordCountAsync(CancellationToken ct) { TextResult.Text += "Getting the word count for Origin of Species...\n"; var client = new HttpClient(); await Task.Delay(500); try { HttpResponseMessage response = await client.GetAsync
    (@"http://www.gutenberg.org/files/2009/2009.txt", ct); var words = await response.Content.ReadAsStringAsync(); var wordArray = words.Split
    (delimiters, StringSplitOptions.RemoveEmptyEntries); return wordArray.Count(); } finally { client.Dispose(); } }

  7. Now, let’s create the Click event handler for our CancelButton. This method just needs to check if CancellationTokenSource is null, and if not, it calls the Cancel method.

    private void CancelButton_Click(object sender, RoutedEventArgs e) { if (cts != null) { cts.Cancel(); } }

  8. Ok, let’s finish up by adding a Click event handler for StartButton. This method is the same as explained before, except we also have a catch block that specifically handles OperationCancelledException. Don’t forget to mark the method with the async modifier.

    public async Task<int> GetWordCountAsync(CancellationToken ct) { TextResult.Text += "Getting the word count for Origin of Species...\n"; var client = new HttpClient(); await Task.Delay(500); try { HttpResponseMessage response = await client.GetAsync
    (@"http://www.gutenberg.org/files/2009/2009.txt", ct); var words = await response.Content.ReadAsStringAsync(); var wordArray = words.Split
    (delimiters, StringSplitOptions.RemoveEmptyEntries); return wordArray.Count(); } finally { client.Dispose(); } }

  9. In Visual Studio 2012, press F5 to run the project Click on the Start button, then the Cancel button. Your application should appear as shown in the following screenshot:

How it works…

Cancellation is an aspect of user interaction that you need to consider to build a professional async application. In this example, we implemented cancellation by using a Cancel button, which is one of the most common ways to surface cancellation functionality in a GUI application.

In this recipe, cancellation follows a very common flow.

  1. The caller (start button click event handler) creates a CancellationTokenSource object.

    private async void StartButton_Click(object sender, RoutedEventArgs e) { cts = new CancellationTokenSource(); ... }

  2. The caller calls a cancelable method, and passes CancellationToken from CancellationTokenSource (CancellationTokenSource.Token).

    public async Task<int> GetWordCountAsync(CancellationToken ct ) { ... HttpResponseMessage response = await client.GetAsync
    (@"http://www.gutenberg.org/files/2009/2009.txt", ct ); ... }

  3. The cancel button click event handler requests cancellation using the CancellationTokenSource object (CancellationTokenSource.Cancel()).

    private void CancelButton_Click(object sender, RoutedEventArgs e) { if (cts != null) { cts.Cancel(); } }

  4. The task acknowledges the cancellation by throwing OperationCancelledException, which we handle in a catch block in the start button click event handler.

LEAVE A REPLY

Please enter your comment!
Please enter your name here