Async and Await

When writing an application, you may come across a situation where some method takes time to execute. This long-running task may cause your application to hang or a user interface to freeze until it completes. The reason for this is because it is executing synchronously, which means that it is running on the main thread and blocking that thread until the operation completes. For years, developers have worked at overcoming this by performing these long-running tasks asynchronously.

Many aspects of asynchronous programming are covered in detail in chapter 19, so this basic concept will be touched on here. Asynchronous programming involves performing long-running tasks, or those that would cause the main thread to pause, on another thread. This other thread would execute in the background, independent of the main thread, which would continue running unhindered. Since both threads are executing concurrently, they are considered asynchronous.

While this approach allows for a much more responsive application, it is somewhat more convoluted and difficult to follow in code and manage. The reason for this is because you are responsible for knowing when the background thread(s) is/are completed and for managing data and synchronicity across them.

The .NET framework has always supported asynchronous programming by providing asynchronous methods in classes where they make the most sense. This would typically be classes that included methods that access data such as reading files, making web calls, or performing communications over a socket.

Performing these actions asynchronously would require you to either handle events, that were fired once a given operation completed, or via Begin and End methods. In the later situation, you would call the appropriate Begin method (such as Socket.BeginAccept) to start the operation asynchronously. This method would require you to provide a callback, which would be executed once the method completed. Within this callback, you would call Socket.EndAccept to complete the operation.

As you can imagine, this can become very confusing and difficult to maintain. It also has the effect of breaking up the flow of your application, which can make it difficult to read and maintain. To help resolve some of these issues, Microsoft created Async and Await, first introduced as a CTP add-on to Visual Studio 2010, but now part of version 4.5 of the .NET framework.

As with the previous section, all examples in this section will update the MainWindow.xaml.vb file.

The Core Concept

Async and Await are almost magical, which you will see when you first use them. Async is a method modifier that is used to identify a method as asynchronous. It can be used with a Sub or Function, if the return type is a Task or Task(Of T). It also works with lambda expressions. It is as simple as just adding the modifier, like this:

Private Async Sub StartTimeConsumingTask()
    ' Some time-consuming task
End Sub

Async is only part of this equation, though. If you create the previous method, you will receive a compiler warning that states:

This async method lacks 'Await' operators and so will run synchronously. 
Consider using the 'Await' operator to await non-blocking API calls, or
'Await Task.Run(...)’ to do CPU-bound work on a background thread.

As Visual Studio was nice enough to tell everyone, you need to use Await. Await and Async are twins and work together at their assigned job of providing asynchronous magic. Await is a method operator usable with any function or lambda expression that returns a Task(OF TResult). You would use Await for your long-running task or the task that you wish to execute asynchronously.

An Example

In order for you to truly understand how this works, you are going to create a new example in your application. Start by adding the following:

Public Sub AsyncBasicExample()
    StartTimeConsumingTask()
    TextBoxResult.Text += "Main thread free for use while operation runs in" +
                          "background" + Environment.NewLine
End Sub

Private Async Sub StartTimeConsumingTask()
    TextBoxResult.Text += "Starting time-consuming task" + Environment.NewLine
    ExampleList.IsEnabled = False
    ExecuteButton.IsEnabled = False

    Await TimeConsumingTask()

    TextBoxResult.Text += "Time-consuming task completed" + Environment.NewLine
    ExampleList.IsEnabled = True
    ExecuteButton.IsEnabled = True
End Sub

Private Function TimeConsumingTask() As Task
    Return Task.Run(Sub() Thread.Sleep(10000))
End Function

Be sure to add the following statement at the top of the code file in order to use the Thread class:

Imports System.Threading

The TimeConsumingTask simply creates a Task and runs it. This task, a lambda expression delegate, simply sleeps for 10 seconds before ending. This serves as simulating a long-running task that runs in the background. Since TimeConsumingTask returns a Task, it is awaitable, which Visual Studio will tell you if you put the cursor over the method name.

The StartTimeConsumingTask method starts by adding some text to the result window and disabling the list box and button on the form. This portion of the method is currently running synchronously. Once the compiler hits the Await it starts running the TimeConsumingMethod in the background and immediately exits the method, returning to the line following the one that called it. This allows the main thread to continue running, which you will see when it writes a new value to the results textbox.


Note
If an exception occurs within a method that has been awaited, the Await operator actually rethrows the exception so that you can catch it and deal with it appropriately.

Once the task completes, control is returned to the method following the awaited method in the StartTimeConsumingTask method. Here you write text saying the task completed and reenable the UI controls that were previously disabled.

Now add the example to the list so you can actually run it:

New With {.Name = "Async and Await - The Basics", _
          .Lambda = New Action(Sub() AsyncBasicExample())}

Once you have completed that, you can run the application and execute the new example. Initially you will see that the UI controls become disabled and the results text box has the following:

Starting time-consuming task
Main thread free for use while operation runs in background

The second line proves that the main thread is not being blocked in any way. You can further prove it by dragging the window around for 10 seconds, while the background task is running. Once that task completes, the results textbox is updated as shown in Figure 5.6.

Figure 5.6 Basic Async/Await example

5.6

While this was a fairly basic example, you can clearly see the power you now have at your hands. Using Async and Await allowed you to very easily execute a task on the background, but it did it while maintaining the readability and maintainability of your code by preserving the flow.

The Man Behind the Curtain

You are no doubt intrigued at this point, but you probably also have many questions related to how this all works. What is the compiler doing to make this all happen?

The best way to show you what is happening is to show you what happens if you don't use Async and Await. Figure 5.7 shows you what the IL, discussed in Chapter 2, looks like if you remove both keywords from the StartTimeConsumingTask method.

Figure 5.7 IL of StartTimeConsumingTask without Async/Await

5.7

Don't worry about understanding this gibberish right now—this is only for comparison purposes. Now look at Figure 5.8, which shows the same method with the Async and Await keywords restored to proper glory.

Figure 5.8 IL of StartTimeConsumingTask with Async/Await

5.8

The first thing you may notice is that the method is much shorter in this version. You may also notice the references to some object named VB$StateMachine_0_StartTimeConsumingTask. A closer look at this class is shown in Figure 5.9.

Figure 5.9 VB$StateMachine_0_StartTimeConsumingTask

5.9

This class is the secret behind Async and Await. The Async modifier told the compiler to create an anonymous class that represents the method. This class is a state machine that keeps track, using a stack, of the locations of each Await operator in order to allow execution to return. Most of this work is handled by the MoveNext method of the state machine.

Using Async and Await

The previous section provided you enough information for you to be able to easily create asynchronous tasks, but the example was very general in order to focus on the core concept itself. This section aims to provide a more concrete example that you can more easily apply to real-world situations.

A very important thing to understand is that Microsoft wasted no time in providing internal support for Async and Await. Nearly any method that returned a Task has been updated, and many older classes have had new awaitable methods added. The rule of thumb is that if a method name ends with the word “Async” it is awaitable and supports the new asynchronous programming model introduced in this version of the framework.

The first thing you are going to do is create a new example that performs an asynchronous task using the old method. To get started, add the following methods to your application:

Public Sub OldAsyncExample()
    RetrieveSongData()
    TextBoxResult.Text += "**Main thread free for use while operation" +
                          "runs in background**" + Environment.NewLine
End Sub

Private Sub RetrieveSongData()
    Dim url As String = "http://lyrics.wikia.com/api.php?artist=Linkin" +
                        "Park&song=Lost in the echo&fmt=xml"

    TextBoxResult.Text += "Attempting to retrieve song lyrics" +
                          Environment.NewLine
    ExampleList.IsEnabled = False
    ExecuteButton.IsEnabled = False

    Using client As New WebClient
        AddHandler client.DownloadStringCompleted, _
            AddressOf DownloadStringCompletedHandler
        client.DownloadStringAsync(New Uri(url))
    End Using
End Sub

Private Sub DownloadStringCompletedHandler(sender As Object, _
            e As DownloadStringCompletedEventArgs)
    TextBoxResult.Text += e.Result
    TextBoxResult.Text += Environment.NewLine + "Completed retrieving song lyrics"
    ExampleList.IsEnabled = True
    ExecuteButton.IsEnabled = True
End Sub

Be sure to add the following statement at the top of the code file in order to use the WebClient class:

Imports System. Net

The OldAsyncExample method runs on the main thread and starts everything running. The RetrieveSongData method calls out to a freely usable rest service to retrieve song lyrics. You use the WebClient.DownloadStringAsync method to asynchronously get the results from calling the REST service. When the asynchronous operation has completed, it fires the DownloadStringCompleted event. You handle this event in order to provide the results and re-enable the UI controls.

Now add the following item to the examples lists:

New With {.Name = "Async and Await - Old Way", _
          .Lambda = New Action(Sub() OldAsyncExample())}

When the example is executed and completed, it will look like Figure 5.10.

Figure 5.10 Old methodology

5.10

This example executes asynchronously, and there is nothing wrong with it. The one main complaint to be made is that your code has been split, breaking the natural flow, in order to handle the completed event. You could alleviate this, to some extent, by using a lambda expression instead.

Since this section is on the new asynchronous programming model, you will create a new example that uses it. Start by updating your application with the following methods:

Public Sub AsyncAdvancedExample()
    RetrieveArtistDataAsync()
    TextBoxResult.Text += "**Main thread free for use while operation" +
                          "runs in background**" + Environment.NewLine
End Sub

Private Async Sub RetrieveArtistDataAsync()

   Dim url As String = "http://lyrics.wikia.com/api.php?artist=Linkin Park&fmt=xml"
   Using client As HttpClient = New HttpClient()
       TextBoxResult.Text += "Attempting to retrieve Linkin Park albums" +
                             Environment.NewLine
       ExampleList.IsEnabled = False
       ExecuteButton.IsEnabled = False

       Dim response As String = Await client.GetStringAsync(url)
       ProcessArtistData(response)

       TextBoxResult.Text += Environment.NewLine + "Completed retrieving albums"
       ExampleList.IsEnabled = True
       ExecuteButton.IsEnabled = True
   End Using
End Sub

In order to use the HttpClient class you will need to add a reference to System.Net.Http to the project. To do this, just right-click on the name of your project (within Solution Explorer) and select “Add Reference” from the context menu. You will then need to add the following statement at the top of the code:

Imports System.Net

For starters, RetrieveArtistDataAsync uses the new HttpClient class. This class is a replacement for WebClient and fully supports Async and Await. The main difference between this method and RetrieveSongData, in the previous example, is that you use the new GetStringAsync method. This method runs on a background thread using Task, which it returns. Since it returns a Task it is awaitable.

Now add the following method, which is responsible for processing the data:

Private Sub ProcessArtistData(rawXmlValue As String)
    TextBoxResult.Text += "Parsing album names from data" + Environment.NewLine

    Using sr As StringReader = New StringReader(rawXmlValue)
        Using reader As XmlReader = XmlReader.Create(sr)
            While reader.Read()
                Select Case reader.NodeType
                    Case XmlNodeType.Element
                        Select Case reader.Name.ToLowerInvariant()
                            Case "album"
                                TextBoxResult.Text += String.Format("{0}{1,-20}",
                                                      Environment.NewLine,
                                                      reader.ReadElementString)
                            Case "year"
                                Dim value As String = reader.ReadElementString
                                TextBoxResult.Text += " [" +
                                    IIf(Not (String.IsNullOrWhiteSpace(value)),
                                    value, "Not Listed") + "]"
                        End Select
                End Select
            End While
        End Using
    End Using

    TextBoxResult.Text += Environment.NewLine + "Complete Parsing album names"
End Sub

Since you make use of the StringReader and XmlReader classes, you will need to add the following import statements to your code:

Imports System.IO
Imports System.Xml

When the method runs, ProcessArtistData will not be called until after the GetStringAsync method completes. This method uses an XmlReader to parse the data and write it to the results text box.


Note
You should be aware that the ProcessArtistData method runs synchronously. If the data processing was more intensive then it is in the example, it could potentially cause the main thread and the UI to block or freeze. However, you could resolve this issue by awaiting the XmlReader.ReadAsync method.

To complete the updates, add the new example to the list:

New With {.Name = "Async and Await - Advanced", _
          .Lambda = New Action(Sub() AsyncAdvancedExample())}

With that updated, you can run the application and execute the new example. It will make a REST call to a service in order to retrieve album information. This operation executes on the background, allowing the application to continue running smoothly. Once the operation completes, control returns to the RetrieveArtistDataAsync method where the results are processed and displayed.

The final result is shown in Figure 5.11.

Figure 5.11 New methodology

5.11

Since you used Async and Await in this scenario, you have retained control of the flow of the application and kept the appearance of the code clean and concise. There are no callbacks or event handlers visible. This new asynchronous programming model provides developers with a much-needed, and deserved, reprieve from the headache of traditional asynchronous development.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset