Walkthrough: Accessing the Web by Using Async and Await (Visual Basic)
You can write asynchronous programs more easily and intuitively by using async/await features. You can write asynchronous code that looks like synchronous code and let the compiler handle the difficult callback functions and continuations that asynchronous code usually entails.
For more information about the Async feature, see Asynchronous Programming with Async and Await (Visual Basic).
This walkthrough starts with a synchronous Windows Presentation Foundation (WPF) application that sums the number of bytes in a list of websites. The walkthrough then converts the application to an asynchronous solution by using the new features.
You can develop the applications by either completing the walkthrough or downloading the sample from the .NET Sample Browser. The example code is in the SerialAsyncExample project.
In this walkthrough, you complete the following tasks:
- Create a WPF application
- Design a simple WPF MainWindow
- Add a reference
- Add necessary Imports statements
- Create a synchronous application
- Test the synchronous solution
- Convert GetURLContents to an asynchronous method
- Convert SumPageSizes to an asynchronous method
- Convert startButton_Click to an asynchronous method
- Test the asynchronous solution
- Replace the GetURLContentsAsync method with a .NET Framework method
See the Example section for the complete asynchronous example.
Prerequisites
Visual Studio 2012 or later must be installed on your computer. For more information, see the Visual Studio Downloads page.
Create a WPF application
Start Visual Studio.
On the menu bar, choose File, New, Project.
The New Project dialog box opens.
In the Installed Templates pane, choose Visual Basic, and then choose WPF Application from the list of project types.
In the Name text box, enter
AsyncExampleWPF
, and then choose the OK button.The new project appears in Solution Explorer.
Design a simple WPF MainWindow
In the Visual Studio Code Editor, choose the MainWindow.xaml tab.
If the Toolbox window isn’t visible, open the View menu, and then choose Toolbox.
Add a Button control and a TextBox control to the MainWindow window.
Highlight the TextBox control and, in the Properties window, set the following values:
Set the Name property to
resultsTextBox
.Set the Height property to 250.
Set the Width property to 500.
On the Text tab, specify a monospaced font, such as Lucida Console or Global Monospace.
Highlight the Button control and, in the Properties window, set the following values:
Set the Name property to
startButton
.Change the value of the Content property from Button to Start.
Position the text box and the button so that both appear in the MainWindow window.
For more information about the WPF XAML Designer, see Creating a UI by using XAML Designer.
Add a reference
In Solution Explorer, highlight your project's name.
On the menu bar, choose Project, Add Reference.
The Reference Manager dialog box appears.
At the top of the dialog box, verify that your project is targeting the .NET Framework 4.5 or higher.
In the Assemblies area, choose Framework if it isn’t already chosen.
In the list of names, select the System.Net.Http check box.
Choose the OK button to close the dialog box.
Add necessary Imports statements
In Solution Explorer, open the shortcut menu for MainWindow.xaml.vb, and then choose View Code.
Add the following
Imports
statements at the top of the code file if they’re not already present.Imports System.Net.Http Imports System.Net Imports System.IO
Create a synchronous application
In the design window, MainWindow.xaml, double-click the Start button to create the
startButton_Click
event handler in MainWindow.xaml.vb.In MainWindow.xaml.vb, copy the following code into the body of
startButton_Click
:resultsTextBox.Clear() SumPageSizes() resultsTextBox.Text &= vbCrLf & "Control returned to startButton_Click."
The code calls the method that drives the application,
SumPageSizes
, and displays a message when control returns tostartButton_Click
.The code for the synchronous solution contains the following four methods:
SumPageSizes
, which gets a list of webpage URLs fromSetUpURLList
and then callsGetURLContents
andDisplayResults
to process each URL.SetUpURLList
, which makes and returns a list of web addresses.GetURLContents
, which downloads the contents of each website and returns the contents as a byte array.DisplayResults
, which displays the number of bytes in the byte array for each URL.
Copy the following four methods, and then paste them under the
startButton_Click
event handler in MainWindow.xaml.vb:Private Sub SumPageSizes() ' Make a list of web addresses. Dim urlList As List(Of String) = SetUpURLList() Dim total = 0 For Each url In urlList ' GetURLContents returns the contents of url as a byte array. Dim urlContents As Byte() = GetURLContents(url) DisplayResults(url, urlContents) ' Update the total. total += urlContents.Length Next ' Display the total count for all of the web addresses. resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf & "Total bytes returned: {0}" & vbCrLf, total) End Sub Private Function SetUpURLList() As List(Of String) Dim urls = New List(Of String) From { "https://msdn.microsoft.com/library/windows/apps/br211380.aspx", "https://msdn.microsoft.com", "https://msdn.microsoft.com/library/hh290136.aspx", "https://msdn.microsoft.com/library/ee256749.aspx", "https://msdn.microsoft.com/library/hh290138.aspx", "https://msdn.microsoft.com/library/hh290140.aspx", "https://msdn.microsoft.com/library/dd470362.aspx", "https://msdn.microsoft.com/library/aa578028.aspx", "https://msdn.microsoft.com/library/ms404677.aspx", "https://msdn.microsoft.com/library/ff730837.aspx" } Return urls End Function Private Function GetURLContents(url As String) As Byte() ' The downloaded resource ends up in the variable named content. Dim content = New MemoryStream() ' Initialize an HttpWebRequest for the current URL. Dim webReq = CType(WebRequest.Create(url), HttpWebRequest) ' Send the request to the Internet resource and wait for ' the response. ' Note: you can't use HttpWebRequest.GetResponse in a Windows Store app. Using response As WebResponse = webReq.GetResponse() ' Get the data stream that is associated with the specified URL. Using responseStream As Stream = response.GetResponseStream() ' Read the bytes in responseStream and copy them to content. responseStream.CopyTo(content) End Using End Using ' Return the result as a byte array. Return content.ToArray() End Function Private Sub DisplayResults(url As String, content As Byte()) ' Display the length of each website. The string format ' is designed to be used with a monospaced font, such as ' Lucida Console or Global Monospace. Dim bytes = content.Length ' Strip off the "https://". Dim displayURL = url.Replace("https://", "") resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes) End Sub
Test the synchronous solution
Choose the F5 key to run the program, and then choose the Start button.
Output that resembles the following list should appear:
msdn.microsoft.com/library/windows/apps/br211380.aspx 383832 msdn.microsoft.com 33964 msdn.microsoft.com/library/hh290136.aspx 225793 msdn.microsoft.com/library/ee256749.aspx 143577 msdn.microsoft.com/library/hh290138.aspx 237372 msdn.microsoft.com/library/hh290140.aspx 128279 msdn.microsoft.com/library/dd470362.aspx 157649 msdn.microsoft.com/library/aa578028.aspx 204457 msdn.microsoft.com/library/ms404677.aspx 176405 msdn.microsoft.com/library/ff730837.aspx 143474 Total bytes returned: 1834802 Control returned to startButton_Click.
Notice that it takes a few seconds to display the counts. During that time, the UI thread is blocked while it waits for requested resources to download. As a result, you can't move, maximize, minimize, or even close the display window after you choose the Start button. These efforts fail until the byte counts start to appear. If a website isn’t responding, you have no indication of which site failed. It is difficult even to stop waiting and close the program.
Convert GetURLContents to an asynchronous method
To convert the synchronous solution to an asynchronous solution, the best place to start is in
GetURLContents
because the calls to the HttpWebRequest.GetResponse method and to the Stream.CopyTo method are where the application accesses the web. The .NET Framework makes the conversion easy by supplying asynchronous versions of both methods.For more information about the methods that are used in
GetURLContents
, see WebRequest.Note
As you follow the steps in this walkthrough, several compiler errors appear. You can ignore them and continue with the walkthrough.
Change the method that's called in the third line of
GetURLContents
fromGetResponse
to the asynchronous, task-based GetResponseAsync method.Using response As WebResponse = webReq.GetResponseAsync()
GetResponseAsync
returns a Task<TResult>. In this case, the task return variable,TResult
, has type WebResponse. The task is a promise to produce an actualWebResponse
object after the requested data has been downloaded and the task has run to completion.To retrieve the
WebResponse
value from the task, apply an Await operator to the call toGetResponseAsync
, as the following code shows.Using response As WebResponse = Await webReq.GetResponseAsync()
The
Await
operator suspends the execution of the current method,GetURLContents
, until the awaited task is complete. In the meantime, control returns to the caller of the current method. In this example, the current method isGetURLContents
, and the caller isSumPageSizes
. When the task is finished, the promisedWebResponse
object is produced as the value of the awaited task and assigned to the variableresponse
.The previous statement can be separated into the following two statements to clarify what happens.
Dim responseTask As Task(Of WebResponse) = webReq.GetResponseAsync() Using response As WebResponse = Await responseTask
The call to
webReq.GetResponseAsync
returns aTask(Of WebResponse)
orTask<WebResponse>
. Then anAwait
operator is applied to the task to retrieve theWebResponse
value.If your async method has work to do that doesn’t depend on the completion of the task, the method can continue with that work between these two statements, after the call to the async method and before the await operator is applied. For examples, see How to: Make Multiple Web Requests in Parallel by Using Async and Await (Visual Basic) and How to: Extend the Async Walkthrough by Using Task.WhenAll (Visual Basic).
Because you added the
Await
operator in the previous step, a compiler error occurs. The operator can be used only in methods that are marked with the Async modifier. Ignore the error while you repeat the conversion steps to replace the call toCopyTo
with a call toCopyToAsync
.Change the name of the method that’s called to CopyToAsync.
The
CopyTo
orCopyToAsync
method copies bytes to its argument,content
, and doesn’t return a meaningful value. In the synchronous version, the call toCopyTo
is a simple statement that doesn't return a value. The asynchronous version,CopyToAsync
, returns a Task. The task functions like "Task(void)" and enables the method to be awaited. ApplyAwait
orawait
to the call toCopyToAsync
, as the following code shows.Await responseStream.CopyToAsync(content)
The previous statement abbreviates the following two lines of code.
' CopyToAsync returns a Task, not a Task<T>. Dim copyTask As Task = responseStream.CopyToAsync(content) ' When copyTask is completed, content contains a copy of ' responseStream. Await copyTask
All that remains to be done in
GetURLContents
is to adjust the method signature. You can use theAwait
operator only in methods that are marked with the Async modifier. Add the modifier to mark the method as an async method, as the following code shows.Private Async Function GetURLContents(url As String) As Byte()
The return type of an async method can only be Task, Task<TResult>. In Visual Basic, the method must be a
Function
that returns aTask
or aTask(Of T)
, or the method must be aSub
. Typically, aSub
method is used only in an async event handler, whereSub
is required. In other cases, you useTask(T)
if the completed method has a Return statement that returns a value of type T, and you useTask
if the completed method doesn’t return a meaningful value.For more information, see Async Return Types (Visual Basic).
Method
GetURLContents
has a return statement, and the statement returns a byte array. Therefore, the return type of the async version is Task(T), where T is a byte array. Make the following changes in the method signature:Change the return type to
Task(Of Byte())
.By convention, asynchronous methods have names that end in "Async," so rename the method
GetURLContentsAsync
.
The following code shows these changes.
Private Async Function GetURLContentsAsync(url As String) As Task(Of Byte())
With those few changes, the conversion of
GetURLContents
to an asynchronous method is complete.
Convert SumPageSizes to an asynchronous method
Repeat the steps from the previous procedure for
SumPageSizes
. First, change the call toGetURLContents
to an asynchronous call.Change the name of the method that’s called from
GetURLContents
toGetURLContentsAsync
, if you haven't already done so.Apply
Await
to the task thatGetURLContentsAsync
returns to obtain the byte array value.
The following code shows these changes.
Dim urlContents As Byte() = Await GetURLContentsAsync(url)
The previous assignment abbreviates the following two lines of code.
' GetURLContentsAsync returns a task. At completion, the task ' produces a byte array. Dim getContentsTask As Task(Of Byte()) = GetURLContentsAsync(url) Dim urlContents As Byte() = Await getContentsTask
Make the following changes in the method's signature:
Mark the method with the
Async
modifier.Add "Async" to the method name.
There is no task return variable, T, this time because
SumPageSizesAsync
doesn’t return a value for T. (The method has noReturn
statement.) However, the method must return aTask
to be awaitable. Therefore, change the method type fromSub
toFunction
. The return type of the function isTask
.
The following code shows these changes.
Private Async Function SumPageSizesAsync() As Task
The conversion of
SumPageSizes
toSumPageSizesAsync
is complete.
Convert startButton_Click to an asynchronous method
In the event handler, change the name of the called method from
SumPageSizes
toSumPageSizesAsync
, if you haven’t already done so.Because
SumPageSizesAsync
is an async method, change the code in the event handler to await the result.The call to
SumPageSizesAsync
mirrors the call toCopyToAsync
inGetURLContentsAsync
. The call returns aTask
, not aTask(T)
.As in previous procedures, you can convert the call by using one statement or two statements. The following code shows these changes.
' One-step async call. Await SumPageSizesAsync() ' Two-step async call. Dim sumTask As Task = SumPageSizesAsync() Await sumTask
To prevent accidentally reentering the operation, add the following statement at the top of
startButton_Click
to disable the Start button.' Disable the button until the operation is complete. startButton.IsEnabled = False
You can reenable the button at the end of the event handler.
' Reenable the button in case you want to run the operation again. startButton.IsEnabled = True
For more information about reentrancy, see Handling Reentrancy in Async Apps (Visual Basic).
Finally, add the
Async
modifier to the declaration so that the event handler can awaitSumPagSizesAsync
.Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click
Typically, the names of event handlers aren’t changed. The return type isn’t changed to
Task
because event handlers must beSub
procedures in Visual Basic.The conversion of the project from synchronous to asynchronous processing is complete.
Test the asynchronous solution
Choose the F5 key to run the program, and then choose the Start button.
Output that resembles the output of the synchronous solution should appear. However, notice the following differences.
The results don’t all occur at the same time, after the processing is complete. For example, both programs contain a line in
startButton_Click
that clears the text box. The intent is to clear the text box between runs if you choose the Start button for a second time, after one set of results has appeared. In the synchronous version, the text box is cleared just before the counts appear for the second time, when the downloads are completed and the UI thread is free to do other work. In the asynchronous version, the text box clears immediately after you choose the Start button.Most importantly, the UI thread isn’t blocked during the downloads. You can move or resize the window while the web resources are being downloaded, counted, and displayed. If one of the websites is slow or not responding, you can cancel the operation by choosing the Close button (the x in the red field in the upper-right corner).
Replace the GetURLContentsAsync method with a .NET Framework method
The .NET Framework provides many async methods that you can use. One of them, the HttpClient.GetByteArrayAsync(String) method, does just what you need for this walkthrough. You can use it instead of the
GetURLContentsAsync
method that you created in an earlier procedure.The first step is to create an HttpClient object in the
SumPageSizesAsync
method. Add the following declaration at the start of the method.' Declare an HttpClient object and increase the buffer size. The ' default buffer size is 65,536. Dim client As HttpClient = New HttpClient() With {.MaxResponseContentBufferSize = 1000000}
In
SumPageSizesAsync
, replace the call to yourGetURLContentsAsync
method with a call to theHttpClient
method.Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)
Remove or comment out the
GetURLContentsAsync
method that you wrote.Choose the F5 key to run the program, and then choose the Start button.
The behavior of this version of the project should match the behavior that the "To test the asynchronous solution" procedure describes but with even less effort from you.
Example
The following is the full example of the converted asynchronous solution that uses the asynchronous GetURLContentsAsync
method. Notice that it strongly resembles the original, synchronous solution.
' Add the following Imports statements, and add a reference for System.Net.Http.
Imports System.Net.Http
Imports System.Net
Imports System.IO
Class MainWindow
Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click
' Disable the button until the operation is complete.
startButton.IsEnabled = False
resultsTextBox.Clear()
'' One-step async call.
Await SumPageSizesAsync()
' Two-step async call.
'Dim sumTask As Task = SumPageSizesAsync()
'Await sumTask
resultsTextBox.Text &= vbCrLf & "Control returned to button1_Click."
' Reenable the button in case you want to run the operation again.
startButton.IsEnabled = True
End Sub
Private Async Function SumPageSizesAsync() As Task
' Make a list of web addresses.
Dim urlList As List(Of String) = SetUpURLList()
Dim total = 0
For Each url In urlList
Dim urlContents As Byte() = Await GetURLContentsAsync(url)
' The previous line abbreviates the following two assignment statements.
'//<snippet21>
' GetURLContentsAsync returns a task. At completion, the task
' produces a byte array.
'Dim getContentsTask As Task(Of Byte()) = GetURLContentsAsync(url)
'Dim urlContents As Byte() = Await getContentsTask
DisplayResults(url, urlContents)
' Update the total.
total += urlContents.Length
Next
' Display the total count for all of the websites.
resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf &
"Total bytes returned: {0}" & vbCrLf, total)
End Function
Private Function SetUpURLList() As List(Of String)
Dim urls = New List(Of String) From
{
"https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
"https://msdn.microsoft.com",
"https://msdn.microsoft.com/library/hh290136.aspx",
"https://msdn.microsoft.com/library/ee256749.aspx",
"https://msdn.microsoft.com/library/hh290138.aspx",
"https://msdn.microsoft.com/library/hh290140.aspx",
"https://msdn.microsoft.com/library/dd470362.aspx",
"https://msdn.microsoft.com/library/aa578028.aspx",
"https://msdn.microsoft.com/library/ms404677.aspx",
"https://msdn.microsoft.com/library/ff730837.aspx"
}
Return urls
End Function
Private Async Function GetURLContentsAsync(url As String) As Task(Of Byte())
' The downloaded resource ends up in the variable named content.
Dim content = New MemoryStream()
' Initialize an HttpWebRequest for the current URL.
Dim webReq = CType(WebRequest.Create(url), HttpWebRequest)
' Send the request to the Internet resource and wait for
' the response.
Using response As WebResponse = Await webReq.GetResponseAsync()
' The previous statement abbreviates the following two statements.
'Dim responseTask As Task(Of WebResponse) = webReq.GetResponseAsync()
'Using response As WebResponse = Await responseTask
' Get the data stream that is associated with the specified URL.
Using responseStream As Stream = response.GetResponseStream()
' Read the bytes in responseStream and copy them to content.
Await responseStream.CopyToAsync(content)
' The previous statement abbreviates the following two statements.
' CopyToAsync returns a Task, not a Task<T>.
'Dim copyTask As Task = responseStream.CopyToAsync(content)
' When copyTask is completed, content contains a copy of
' responseStream.
'Await copyTask
End Using
End Using
' Return the result as a byte array.
Return content.ToArray()
End Function
Private Sub DisplayResults(url As String, content As Byte())
' Display the length of each website. The string format
' is designed to be used with a monospaced font, such as
' Lucida Console or Global Monospace.
Dim bytes = content.Length
' Strip off the "https://".
Dim displayURL = url.Replace("https://", "")
resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes)
End Sub
End Class
The following code contains the full example of the solution that uses the HttpClient
method, GetByteArrayAsync
.
' Add the following Imports statements, and add a reference for System.Net.Http.
Imports System.Net.Http
Imports System.Net
Imports System.IO
Class MainWindow
Async Sub startButton_Click(sender As Object, e As RoutedEventArgs) Handles startButton.Click
resultsTextBox.Clear()
' Disable the button until the operation is complete.
startButton.IsEnabled = False
' One-step async call.
Await SumPageSizesAsync()
' Two-step async call.
'Dim sumTask As Task = SumPageSizesAsync()
'Await sumTask
resultsTextBox.Text &= vbCrLf & "Control returned to button1_Click."
' Reenable the button in case you want to run the operation again.
startButton.IsEnabled = True
End Sub
Private Async Function SumPageSizesAsync() As Task
' Declare an HttpClient object and increase the buffer size. The
' default buffer size is 65,536.
Dim client As HttpClient =
New HttpClient() With {.MaxResponseContentBufferSize = 1000000}
' Make a list of web addresses.
Dim urlList As List(Of String) = SetUpURLList()
Dim total = 0
For Each url In urlList
' GetByteArrayAsync returns a task. At completion, the task
' produces a byte array.
Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)
' The following two lines can replace the previous assignment statement.
'Dim getContentsTask As Task(Of Byte()) = client.GetByteArrayAsync(url)
'Dim urlContents As Byte() = Await getContentsTask
DisplayResults(url, urlContents)
' Update the total.
total += urlContents.Length
Next
' Display the total count for all of the websites.
resultsTextBox.Text &= String.Format(vbCrLf & vbCrLf &
"Total bytes returned: {0}" & vbCrLf, total)
End Function
Private Function SetUpURLList() As List(Of String)
Dim urls = New List(Of String) From
{
"https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
"https://msdn.microsoft.com",
"https://msdn.microsoft.com/library/hh290136.aspx",
"https://msdn.microsoft.com/library/ee256749.aspx",
"https://msdn.microsoft.com/library/hh290138.aspx",
"https://msdn.microsoft.com/library/hh290140.aspx",
"https://msdn.microsoft.com/library/dd470362.aspx",
"https://msdn.microsoft.com/library/aa578028.aspx",
"https://msdn.microsoft.com/library/ms404677.aspx",
"https://msdn.microsoft.com/library/ff730837.aspx"
}
Return urls
End Function
Private Sub DisplayResults(url As String, content As Byte())
' Display the length of each website. The string format
' is designed to be used with a monospaced font, such as
' Lucida Console or Global Monospace.
Dim bytes = content.Length
' Strip off the "https://".
Dim displayURL = url.Replace("https://", "")
resultsTextBox.Text &= String.Format(vbCrLf & "{0,-58} {1,8}", displayURL, bytes)
End Sub
End Class
See also
- Async Sample: Accessing the Web Walkthrough (C# and Visual Basic)
- Await Operator
- Async
- Asynchronous Programming with Async and Await (Visual Basic)
- Async Return Types (Visual Basic)
- Task-based Asynchronous Programming (TAP)
- How to: Extend the Async Walkthrough by Using Task.WhenAll (Visual Basic)
- How to: Make Multiple Web Requests in Parallel by Using Async and Await (Visual Basic)