在演练:使用 Async 和 Await 访问 Web(Visual Basic)中,可以通过使用Task.WhenAll方法来改进异步解决方案的性能。 此方法异步等待多个异步操作,这些操作被表示为任务集合。
你可能已在演练中注意到网站以不同速率进行下载。 有时其中一个网站速度非常慢,这会延迟所有剩余的下载。 在演练中运行生成的异步解决方案时,如果不想等待,则可以轻松结束程序,但更好的选择是同时启动所有下载,让更快的下载继续,而无需等待延迟的下载。
可将 Task.WhenAll
方法应用于任务的集合。
WhenAll
的应用程序返回单个任务,直到集合中的每个任务都已完成之后,该任务才会完成。 任务似乎并行运行,但不会创建其他线程。 任务可以按任意顺序完成。
重要
下面的过程介绍演练:使用 Async 和 Await 访问 Web (Visual Basic) 中开发的异步应用程序的扩展。 可以通过完成演练或从 .NET 示例浏览器下载示例来开发应用程序。 示例代码位于 SerialAsyncExample 项目中。
若要运行该示例,必须在计算机上安装 Visual Studio 2012 或更高版本。
将 Task.WhenAll 添加到 GetURLContentsAsync 解决方案
向
ProcessURLAsync
中开发的第一个应用程序添加 方法。如果从 开发人员代码示例下载了代码,请打开 AsyncWalkthrough 项目,然后添加到
ProcessURLAsync
MainWindow.xaml.vb文件。如果你通过完成演练开发了代码,请将 `
ProcessURLAsync
` 添加到包含 `GetURLContentsAsync
` 方法的应用程序中。 此应用程序的MainWindow.xaml.vb文件是“演练中的完整代码示例”部分中的第一个示例。
ProcessURLAsync
方法整合了原始演练的For Each
中的SumPageSizesAsync
循环体中的操作。 该方法以异步方式将指定网站的内容下载为字节数组,然后显示并返回字节数组的长度。Private Async Function ProcessURLAsync(url As String) As Task(Of Integer) Dim byteArray = Await GetURLContentsAsync(url) DisplayResults(url, byteArray) Return byteArray.Length End Function
注释禁止或删除
For Each
中的SumPageSizesAsync
循环,如以下代码所示。'Dim total = 0 'For Each url In urlList ' Dim urlContents As Byte() = Await GetURLContentsAsync(url) ' ' The previous line abbreviates the following two assignment statements. ' ' 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
创建任务集合。 以下代码定义了一个 查询 ,该方法执行 ToArray 时,将创建下载每个网站内容的任务集合。 查询被评估时,任务将被启动。
在声明
SumPageSizesAsync
后将以下代码添加到方法urlList
中。' Create a query. Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) = From url In urlList Select ProcessURLAsync(url) ' Use ToArray to execute the query and start the download tasks. Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
将
Task.WhenAll
应用于任务集合downloadTasks
。Task.WhenAll
返回一个任务,该任务在任务集合中的所有任务都已完成时完成。在以下示例中,表达式
Await
等待WhenAll
返回的单个任务完成。 表达式的计算结果为整数数组,其中每个整数都是下载的网站长度。 将以下代码紧接着添加到上一步中添加的代码之后的SumPageSizesAsync
中。' Await the completion of all the running tasks. Dim lengths As Integer() = Await Task.WhenAll(downloadTasks) '' The previous line is equivalent to the following two statements. 'Dim whenAllTask As Task(Of Integer()) = Task.WhenAll(downloadTasks) 'Dim lengths As Integer() = Await whenAllTask
最后,使用 Sum 该方法计算所有网站的长度之和。 将以下行添加到
SumPageSizesAsync
。Dim total = lengths.Sum()
将 Task.WhenAll 添加到 HttpClient.GetByteArrayAsync 解决方案
向
ProcessURLAsync
中开发的第二个应用程序添加以下版本的 。如果从 开发人员代码示例下载了代码,请打开AsyncWalkthrough_HttpClient项目,然后添加到
ProcessURLAsync
MainWindow.xaml.vb文件。如果您通过完成演练开发了代码,请将
ProcessURLAsync
添加到使用HttpClient.GetByteArrayAsync
方法的应用程序中。 此应用程序的MainWindow.xaml.vb文件是“演练完整代码示例”部分中的第二个示例。
ProcessURLAsync
方法整合了原始演练的For Each
中的SumPageSizesAsync
循环体中的操作。 该方法以异步方式将指定网站的内容下载为字节数组,然后显示并返回字节数组的长度。与上面过程中的
ProcessURLAsync
方法的唯一区别是使用 HttpClient 实例client
。Private Async Function ProcessURLAsync(url As String, client As HttpClient) As Task(Of Integer) Dim byteArray = Await client.GetByteArrayAsync(url) DisplayResults(url, byteArray) Return byteArray.Length End Function
注释禁止或删除
For Each
中的SumPageSizesAsync
循环,如以下代码所示。'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
定义 一个查询 ,该方法执行 ToArray 时,会创建下载每个网站内容的任务集合。 查询被评估时,任务将被启动。
在声明
SumPageSizesAsync
和client
之后,将以下代码添加到方法urlList
中。' Create a query. Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) = From url In urlList Select ProcessURLAsync(url, client) ' Use ToArray to execute the query and start the download tasks. Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
接下来,将
Task.WhenAll
应用于任务集合downloadTasks
。Task.WhenAll
返回一个任务,该任务在任务集合中的所有任务都已完成时完成。在以下示例中,表达式
Await
等待WhenAll
返回的单个任务完成。 完成后,表达式Await
的计算结果为整数数组,其中每个整数都是下载的网站长度。 将以下代码紧接着添加到上一步中添加的代码之后的SumPageSizesAsync
中。' Await the completion of all the running tasks. Dim lengths As Integer() = Await Task.WhenAll(downloadTasks) '' The previous line is equivalent to the following two statements. 'Dim whenAllTask As Task(Of Integer()) = Task.WhenAll(downloadTasks) 'Dim lengths As Integer() = Await whenAllTask
最后,使用 Sum 该方法获取所有网站的长度之和。 将以下行添加到
SumPageSizesAsync
。Dim total = lengths.Sum()
测试 Task.WhenAll 解决方案
对于任一解决方案,请选择 F5 键以运行程序,然后选择 “开始 ”按钮。 输出应类似于 演练:使用 Async 和 Await 访问 Web 的异步解决方案的输出(Visual Basic)。 但是,请注意,网站每次都按不同的顺序显示。
示例 1
以下代码显示了使用 GetURLContentsAsync
该方法从 Web 下载内容的项目的扩展。
' 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()
' 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."
End Sub
Private Async Function SumPageSizesAsync() As Task
' Make a list of web addresses.
Dim urlList As List(Of String) = SetUpURLList()
' Create a query.
Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
From url In urlList Select ProcessURLAsync(url)
' Use ToArray to execute the query and start the download tasks.
Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
' You can do other work here before awaiting.
' Await the completion of all the running tasks.
Dim lengths As Integer() = Await Task.WhenAll(downloadTasks)
'' The previous line is equivalent to the following two statements.
'Dim whenAllTask As Task(Of Integer()) = Task.WhenAll(downloadTasks)
'Dim lengths As Integer() = Await whenAllTask
Dim total = lengths.Sum()
'Dim total = 0
'For Each url In urlList
' Dim urlContents As Byte() = Await GetURLContentsAsync(url)
' ' The previous line abbreviates the following two assignment statements.
' ' 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
'NextNext
' Display the total count for all of the web addresses.
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",
"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
' The actions from the foreach loop are moved to this async method.
Private Async Function ProcessURLAsync(url As String) As Task(Of Integer)
Dim byteArray = Await GetURLContentsAsync(url)
DisplayResults(url, byteArray)
Return byteArray.Length
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()
' 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.
' CopyToAsync returns a Task, not a Task<T>.
Await responseStream.CopyToAsync(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
End Class
示例 2
以下代码显示了使用方法 HttpClient.GetByteArrayAsync
从 Web 下载内容的项目的扩展。
' 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()
'' 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."
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()
' Create a query.
Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
From url In urlList Select ProcessURLAsync(url, client)
' Use ToArray to execute the query and start the download tasks.
Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
' You can do other work here before awaiting.
' Await the completion of all the running tasks.
Dim lengths As Integer() = Await Task.WhenAll(downloadTasks)
'' The previous line is equivalent to the following two statements.
'Dim whenAllTask As Task(Of Integer()) = Task.WhenAll(downloadTasks)
'Dim lengths As Integer() = Await whenAllTask
Dim total = lengths.Sum()
''<snippet7>
'Dim total = 0
'For Each url In urlList
' ' GetByteArrayAsync returns a task. At completion, the task
' ' produces a byte array.
' '<snippet31>
' Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)
' '</snippet31>
' ' 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
'NextNext
' Display the total count for all of the web addresses.
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://www.msdn.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 ProcessURLAsync(url As String, client As HttpClient) As Task(Of Integer)
Dim byteArray = Await client.GetByteArrayAsync(url)
DisplayResults(url, byteArray)
Return byteArray.Length
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