- Overview of Asynchrony
- The Old-Fashioned Way: Event-Based Asynchrony
- The Old-Fashioned Way: The Asynchronous Programming Model
- The Modern Way: The Async Pattern
- Getting Started with Async/ Await
- Exception Handling in Async
- Implementing Task-Based Asynchrony
- Cancellation and Progress
- Asynchronous Lambda Expressions
- Asynchronous I/O File Operations in .NET 4.6
- Debugging Tasks
- Summary
Getting Started with Async/Await
In this section you see the Async pattern with an example based on retrieving information from the Internet. You will build a WPF application that downloads RSS feeds information from the Web, simulating a long-running process over a network. You first, though, create an application that works synchronously; then you see how to implement the Event-based Asynchronous Pattern described at the beginning of this chapter. Finally, you learn how things change in a third example built using the new Async and Await keywords.
The Synchronous Approach
Create a new WPF project with Visual Basic 2015 and .NET Framework 4.6. The application will consume the Visual Basic RSS feed exposed by the Microsoft’s Channel9 website, with particular regard to the list of published videos. Each item in the feed has a large number of properties, but for the sake of simplicity only the most important will be presented in the application’s UI. So, the first thing you need to do is create a class that represents a single video described in the feed. Listing 42.1 demonstrates how to implement a class called Video.
LISTING 42.1 Representing a Single Video
Public Class Video Public Property Title As String Public Property Url As String Public Property Thumbnail As String Public Property DateRecorded As String Public Property Speaker As String Public Shared FeedUrl As String = _ "http://channel9.msdn.com/Tags/visual+basic/RSS" End Class
Notice that all the properties are of type String just to represent values as they exactly come from the feed. Also, a shared field contains the feed URL. Now open the MainWindow.xaml file, to prepare the application’s user interface. The goal is to show the videos’ thumbnails and summary information and to provide the ability to click a thumbnail to open the video in its original location. The ListBox control is a good choice to display a collection of items. This will be placed inside the default Grid panel. Each item in the ListBox will be presented via a custom template made of a Border and a StackPanel that contains an Image control (for the video thumbnail) and a number of TextBlock controls that are bound to properties of the Video class. Listing 42.2 shows the full code for the main window.
LISTING 42.2 Implementing the Application’s User Interface
<Window x:Class="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 Name="VideoBox" ItemsSource="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel VirtualizingPanel.IsVirtualizing="True"/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Border BorderBrush="Black" Margin="5" BorderThickness="2" Tag={Binding Url} MouseLeftButtonUp="Border_MouseLeftButtonUp_1" Width="200" Height="220"> <StackPanel> <Image Source="{Binding Thumbnail}" Width="160" Height="120" /> <TextBlock Text="{Binding Title}" TextWrapping="Wrap" Grid.Row="1"/> <TextBlock Text="{Binding DateRecorded}" Grid.Row="2"/> <TextBlock Text="{Binding Speaker}" Grid.Row="3"/> </StackPanel> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window>
It is worth mentioning that the code replaces the default items container (a VirtualizingStackPanel) with a WrapPanel container so that items are not forced to be presented on one line horizontally. This requires disabling the horizontal scrollbar on the ListBox (ScrollViewer.HorizontalScrollBarVisibility="Disabled") and changing the ListBox.ItemsPanel content with the WrapPanel. Also notice how the Border.Tag property is bound to the Url property of the Video class. This enables you to store the video’s URL and click the Border at runtime to open the video in its original location. Now switch to the code-behind file. The first thing you must do is add a number of Imports directives, some for importing XML namespaces needed to map information from the RSS feed and some for working with additional .NET classes:
Imports System.Net Imports <xmlns:media="http://search.yahoo.com/mrss/"> Imports <xmlns:dc="http://purl.org/dc/elements/1.1/">
The next step is implementing a method that queries the RSS feed returning the list of videos. In this first implementation, you will use a synchronous approach, which will block the user interface when the application is running:
Private Function QueryVideos() As IEnumerable(Of Video) Dim client As New WebClient Dim data As String = client.DownloadString(New Uri(Video.FeedUrl)) Dim doc As XDocument = XDocument.Parse(data) Dim query = From video In doc...<item> Select New Video With { .Title = video.<title>.Value, .Speaker = video.<dc:creator>.Value, .Url = video.<link>.Value, .Thumbnail = video...<media:thumbnail>. FirstOrDefault?.@url, .DateRecorded = String.Concat("Recorded on ", Date.Parse(video.<pubDate>.Value, Globalization.CultureInfo.InvariantCulture). ToShortDateString)} Return query End Function
The code is simple. An instance of the WebClient class, which provides simplified access to networked resources, is created and the invocation of its DownloadString method downloads the entire content of the feed under the form of a String object. Notice that this is the point at which the user interface gets blocked. In fact, it will need to wait for DownloadString to complete the operation before returning to be responsive. After the feed has been downloaded, it is converted into an object of type XDocument and a LINQ query enables you to retrieve all the needed information (refer to Chapter 27, “Manipulating XML Documents with LINQ and XML Literals,” for further information on LINQ to XML). Finally, a method called LoadVideos will run the query and assign the result to the Window’s DataContext; such a method will be invoked at startup. You can change this type of implementation, but it will be more useful later when making comparisons with the asynchronous implementation. The following code demonstrates this, plus the event handler for the MouseLeftButtonUp event of the Border control, where you launch the video in its original web page:
Private Sub LoadVideos() Me.DataContext = QueryVideos() End Sub Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded LoadVideos() End Sub Private Sub Border_MouseLeftButtonUp_1(sender As Object, e As MouseButtonEventArgs) 'Tag is of type Object so an explicit conversion to String is required Dim instance = CType(sender, Border) Process.Start(CStr(instance.Tag)) End Sub
You can now run the application. Figure 42.3 shows the result of the query over the video feed.
FIGURE 42.3 Loading an RSS feed the synchronous way.
The application works as expected, but the real problem with this approach is that the user cannot interact with the interface while the query is running. The reason is that the query’s code is running in the UI thread, so the user interface is busy with the query and does not accept any interaction. This can be easily demonstrated by attempting to move the window while the query is running because you will not be able to move it elsewhere. This has other implications: you cannot refresh controls that display the status of the task because they would be refreshed only when the query completes. Also, you cannot enable users to cancel the operation because you would need a button that the user would never be able to click.
Event-Based Asynchrony and Callbacks
A much better approach is moving the long-running operation into a separate thread, so that the UI can remain responsive while the other thread executes the operation. Lots of classes in the .NET Framework, especially those whose job is interacting with the Web and with networks-expose event-based asynchrony through methods that launch and execute an operation on a separate thread and raise an event when it is completed, passing the result to the caller via a callback. The WebClient class has an asynchronous counterpart of DownloadString, called DownloadStringAsync, that you can use to execute the code on a separate thread and wait for the query result via a callback. The following code demonstrates how to accomplish this (do not worry if you notice something wrong because an explanation is provided in moments):
Private Function QueryVideos() As IEnumerable(Of Video) Dim client As New WebClient Dim query As IEnumerable(Of Video) AddHandler client.DownloadStringCompleted, Sub(sender, e) If e.Error IsNot Nothing Then 'Error handling logic here.. End If Dim doc = _ XDocument.Parse(e.Result) Dim query = From video In doc...<item> Select _ New Video With { .Title = video.<title>.Value, .Speaker = video.<dc:creator>. Value, .Url = video.<link>.Value, .Thumbnail = video...<media:thumbnail>. FirstOrDefault?.@url, .DateRecorded = String.Concat("Recorded on ", Date.Parse(video. <pubDate>.Value, Globalization.CultureInfo. InvariantCulture). ToShortDateString)} End Sub client.DownloadStringAsync(New Uri(Video.FeedUrl)) Return query End Function
The code specifies a statement lambda as an event handler for the DownloadString-Completed event, instead of declaring a separate delegate and pointing to this via an AddressOf clause. The e object is of type DownloadStringCompletedEventArgs and contains the result of the operation. The problem in this code is that the Return statement does not work because it is attempting to return a result that has not been produced yet. On the other side, you cannot write something like this:
Private Function QueryVideos() As IEnumerable(Of Video) Dim client As New WebClient Dim query As IEnumerable(Of Video) AddHandler client.DownloadStringCompleted, Sub(sender, e) If e.Error IsNot Nothing Then 'Error handling logic here.. End If Dim doc = _ XDocument.Parse(e.Result) Dim query = From ... Return query End Sub client.DownloadStringAsync(New Uri(Video.FeedUrl)) End Function
This code does not work because you cannot return a result from a Sub and because it should be returned from the outer method, not the inner. In conclusion, Return statements do not work well with event-based asynchrony. The solution at this point is returning the result via a callback and an Action(Of T) object. So the appropriate implementation of the QueryVideos method in this approach is the following:
Private Sub QueryVideos(listOfVideos As Action(Of IEnumerable(Of Video), Exception)) Dim client As New WebClient AddHandler client.DownloadStringCompleted, Sub(sender, e) If e.Error IsNot Nothing Then listOfVideos(Nothing, e.Error) Return End If Dim doc = _ XDocument.Parse(e.Result) Dim query = From video In doc...<item> Select New Video With { .Title = video.<title>.Value, .Speaker = video.<dc:creator>. Value, .Url = video.<link>.Value, .Thumbnail = video...<media:thumbnail>. FirstOrDefault?.@url, .DateRecorded = String.Concat("Recorded on ", Date.Parse(video. <pubDate>.Value, Globalization.CultureInfo. InvariantCulture). ToShortDateString)} listOfVideos(query, Nothing) End Sub Try client.DownloadStringAsync(New Uri(Video.FeedUrl)) Catch ex As Exception listOfVideos(Nothing, ex) End Try End Sub
The previous code does the following:
- Holds the list of videos from the RSS feed in an Action(Of IEnumerable(Of Video), Exception) object. The Exception instance here is useful to determine whether an error occurred during the query execution.
- If the query completes successfully, the query result is passed to the Action object (that is, the callback).
- If an exception occurs during the query execution (see the first If block inside the statement lambda), the callback receives Nothing as the first parameter because the collection of items was not retrieved successfully and the exception instance as the second parameter.
- If an exception occurs immediately when the web request is made, the callback still receives Nothing and the exception instance. This is at the Try..Catch block level.
So using a callback here has been necessary for two reasons: sending the query result back to the caller correctly and handling two exception scenarios. But you are not done yet. In fact, you have to completely rewrite the LoadVideos method to hold the result of the callback and determine whether the operation completed successfully before assigning the query result to the Window’s DataContext. The following code demonstrates this:
Private Sub LoadVideos() Dim action As Action(Of IEnumerable(Of Video), Exception) = Nothing action = Sub(videos, ex) If ex IsNot Nothing Then MessageBox.Show(ex.Message) Return End If If (videos.Any) Then Me.DataContext = videos Else QueryVideos(action) End If End Sub QueryVideos(action) End Sub
As you can see, the code is not easy, unless you are an expert. There is an invocation to the previous implementation of QueryVideos, passing the instance of the callback. When the result is sent back, the statement lambda first checks for exceptions and, if not, takes the query result as the data source. If you now run the application again, you will get the same result shown in Figure 42.3; however, this time the user interface is responsive and the user can interact with it. But reaching this objective had costs. You had to completely rewrite method implementations and write code that is complex and difficult to read and to extend. So, the multithreading in this situation has not been very helpful. This is the point in which the Async/Await pattern comes in to make things simple.
Asynchrony with Async/Await
The Async/Await pattern has the goal of simplifying the way developers write asynchronous code. You will learn a lot about the underlying infrastructure, but before digging into that, it is important for you to see how your code can be much cleaner and readable. Let’s start by modifying the QueryVideos method to make some important considerations:
Private Async Function QueryVideosAsync() As _ Task(Of IEnumerable(Of Video)) Dim client As New WebClient Dim data = Await client.DownloadStringTaskAsync(New Uri(Video.FeedUrl)) Dim doc = XDocument.Parse(data) Dim query = From video In doc...<item> Select New Video With { .Title = video.<title>.Value, .Speaker = video.<dc:creator>.Value, .Url = video.<link>.Value, .Thumbnail = video...<media:thumbnail>. FirstOrDefault?.@url, .DateRecorded = String.Concat("Recorded on ", Date.Parse(video.<pubDate>.Value, Globalization.CultureInfo.InvariantCulture). ToShortDateString)} Return query End Function
Asynchronous methods must be decorated with the Async modifier. When the compiler encounters this modifier, it expects that the method body contains one or more Await statements. If not, it reports a warning saying that the method will be treated as synchronous, suggesting that the Async modifier should be removed. Async methods must return an object of type Task. If the method returns a value (Function), then it must return a Task(Of T) where T is the actual result type. Otherwise, if the method returns no value, both following syntaxes are allowed:
Async Function TestAsync() As Task 'You can avoid Return statements, the compiler assumes returning no values End Function Async Sub TestAsync() '... End Sub
The difference between the two implementations is that the first one can be called inside another method with Await, but the second one cannot (because it does not need to be awaited). A typical example of the second syntax is about event handlers: they can be asynchronous and can use Await, but no other method will wait for their result. By convention, the suffix of asynchronous methods is the Async literal. This is why QueryVideos has been renamed into QueryVideosAsync. An exception is represented by asynchronous methods already existing in previous versions of the .NET Framework, based on the EAP, whose name already ends with Async. In this case Async is replaced with TaskAsync. For instance (as you discover in moments), the DownloadStringAsync method in the WebClient class has a new counterpart called DownloadStringTaskAsync. Any method that returns a Task or Task(Of T) can be used with Await. With Await, a task is started but the control flow is immediately returned to the caller. The result of the task will not be returned immediately, but later and only when the task completes. But because the control flow immediately returns to the caller, the caller remains responsive. Await can be thought as of a placeholder for the task’s result, which will be available after the awaited task completes. In the previous QueryVideosAsync method, Await starts the WebClient.DownloadStringTaskAsync method and literally waits for its result but, while waiting, the control flow does not move to DownloadStringAsyncTask, while it remains in the caller. Because in the current example the code is running in the UI thread, the user interface remains responsive because the requested task is being executed asynchronously.
In other words, what Await actually does is sign up the rest of the method as a callback on the task, returning immediately. When the task that is being awaited completes, it will invoke the callback and will resume the execution from the exact point it was left.
After the operation has been completed, the rest of the code can elaborate the result. With this kind of approach, your method looks much simpler, like the first synchronous version, but it is running asynchronously with only three edits (the Async modifier, Task(Of T) as the return type, and the Await operator).
Continuing considerations on the previous method, take a look at the final Return statement. It is returning an IEnumerable(Of Video), but actually the method’s signature requires returning a Task(Of IEnumerable(Of Video)). This is possible because the compiler automatically makes Return statements to return a Task-based version of their result even if they do not. As a result, you will not get confused because you will write the same code but the compiler will take care of converting the return type into the appropriate type. This also makes migration of synchronous code to asynchronous easier. Technically speaking, the compiler synthesizes a new Task(Of T) object at the first Await in the method. This Task(Of T) object is returned to the caller at the first Await. Later on, when it encounters a Return statement, the compiler causes that already-existing Task(Of T) object to transition from a “not yet completed” state into the “completed with result” state. Continuing the migration of the code example to the Async/Await pattern, you now need a few edits to the LoadVideos method. The following code demonstrates this:
Private Async Function LoadVideosAsync() As Task Me.DataContext = Await QueryVideosAsync() End Sub
The method is now called LoadVideoAsync and marked with the Async modifier. The reason is that it contains an Await expression that invokes the QueryVideosAsync method. The result of this invocation is taken as the main window’s data source. Finally, you have to edit the MainWindow_Loaded event handler and make it asynchronous like this:
Private Async Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded Await LoadVideosAsync() End Sub
If you now run the application, you will still get a responsive user interface that you can interact with while the long-running task (the query) is executing, but you have achieved this by modifying existing code with very few edits.
How Async and Await Work Behind the Scenes
Behind the scenes of the ease of the Async pattern, the compiler does incredible work to make the magic possible. When you make an asynchronous call by using Await, that invocation starts a new instance of the Task class. As you know from Chapter 41, one thread can contain multiple Task instances. So you might have the asynchronous operation running in the same thread but on a new Task. Internally, it’s as if the compiler could split an asynchronous method in two parts, a method and its callback. If you consider the QueryVideosAsync shown previously, you could imagine a method defined until the invocation of Await. The next part of the method is moved into a callback that is invoked after the awaited operation is completed. This has two benefits. The first benefit is that it ensures that code that needs to manipulate the result of an awaited operation will work with the actual result, which has been returned after completion of the task (this is in fact the moment in which the callback is invoked). Second, such callback is invoked in the same calling thread, which avoids the need of managing threads manually or using the Dispatcher class in technologies like WPF or Silverlight. Figure 42.4 gives you an ideal representation of how the asynchronous method has been split.
FIGURE 42.4 An asynchronous method is split into two ideal parts; the second is a callback.
Beyond considerations like the ones about threading, it is interesting to analyze the code the compiler generated to make asynchrony so efficient in Visual Basic 2015. For this exercise, you need a decompiler tool such as .NET Reflector from Red-Gate, which is available as a free trial from https://www.red-gate.com/products/dotnet-development/reflector/. If you open the compiled .exe file with a tool like this, you can see that the implementation of asynchronous methods is completely different from the one you wrote and that the compiler generated several structures that implement a state machine that supports asynchrony. Figure 42.5 shows the QueryVideosAsync real implementation.
FIGURE 42.5 Investigating the actual generated code with .NET Reflector.
For your convenience, the following is the auto-generated code for QueryVideosAsync:
<AsyncStateMachine(GetType(VB$StateMachine_1_QueryVideosAsync))> _ Private Function QueryVideosAsync() As Task(Of IEnumerable(Of Video)) Dim stateMachine As New VB$StateMachine_1_QueryVideosAsync With { _ .$VB$Me = Me, _ .$State = -1, _ .$Builder = AsyncTaskMethodBuilder(Of IEnumerable(Of Video)).Create _ } stateMachine.$Builder. Start(Of VB$StateMachine_1_QueryVideosAsync)(stateMachine) Return stateMachine.$Builder.Task End Function
You do not need to know the code in detail because this implementation is purely internal; however, the real code relies on the AsyncTaskMethodBuilder class, which creates an instance of an asynchronous method and requires specifying a state machine that controls the execution of the asynchronous task (which is returned once completed). For each asynchronous method, the compiler generated an object representing the state machine. For instance, the compiler generated an object called VB$StateMachine_1_QueryVideosAsync that represents the state machine that controls the execution of the QueryVideosAsync method. Listing 42.3 contains the code of the aforementioned structure.
LISTING 42.3 Internal Implementation of a State Machine for Async Methods
<CompilerGenerated> _ Private NotInheritable Class VB$StateMachine_1_QueryVideosAsync Implements IAsyncStateMachine ' Methods Public Sub New() <CompilerGenerated> _ Friend Sub MoveNext() Implements IAsyncStateMachine.MoveNext <DebuggerNonUserCode> _ Private Sub SetStateMachine(stateMachine As IAsyncStateMachine) _ Implements IAsyncStateMachine.SetStateMachine ' Fields Friend $A0 As TaskAwaiter(Of String) Public $Builder As AsyncTaskMethodBuilder(Of IEnumerable(Of Video)) Public $State As Integer Friend $VB$Me As MainWindow Friend $VB$ResumableLocal_client$0 As WebClient Friend $VB$ResumableLocal_data$1 As String Friend $VB$ResumableLocal_doc$2 As XDocument Friend $VB$ResumableLocal_query$3 As IEnumerable(Of Video) End Class
The code in Listing 42.3 is certainly complex, and you are not required to know how it works under the hood, but focus for a moment on the MoveNext method. This method is responsible of the asynchronous execution of tasks; depending on the state of the task, it resumes the execution at the appropriate point. You can see how the compiler translates the Await keyword into an instance of the TaskAwaiter structure, which is assigned with the result of the invocation to the Task.GetAwaiter method (both are for compiler-use only). If you compare the result of this analysis with the ease of usage of the Async pattern, it is obvious that the compiler does tremendous work to translate that simplicity into a very efficient asynchronous mechanism.
Documentation and Examples of the Async Pattern
Microsoft offers a lot of useful resources to developers who want to start coding the new way. The following list summarizes several resources that you are strongly encouraged to visit to make your learning of Async complete:
- Visual Studio Asynchronous Programming: The official developer center for Async. Here you can find documentation, downloads, instructional videos, and more on language specifications. It is available at http://msdn.microsoft.com/en-us/vstudio/async.aspx.
- 101 Async Samples: An online page that contains a huge number of code examples based on Async for both Visual Basic and Visual C#. You can find samples at http://www.wischik.com/lu/AsyncSilverlight/AsyncSamples.html.
- Sample code: Available on the MSDN Code Gallery (http://code.msdn.microsoft.com).
Do not leave out of your bookmarks the root page of the .NET Framework 4.5 and 4.6 documentation (http://msdn.microsoft.com/en-us/library/w0x726c2).