如何:使用 Task.WhenAll 扩展异步演练 (Visual Basic)
可以使用 Task.WhenAll 方法来改进演练:使用 Async 和 Await 访问 Web (Visual Basic) 中的异步解决方案性能。 此方法以异步方式等待多个异步操作(它们表示为任务的集合)。
你可能已在演练中注意到网站以不同速率进行下载。 有时一个网站非常慢,这会延迟所有其余下载。 运行在演练中生成的异步解决方案时,如果不想等待,则可以方便地结束程序,但更好的选项是同时启动所有下载,并让较快的下载继续进行而不等待延迟的下载。
可将 Task.WhenAll
方法应用于任务的集合。 WhenAll
的应用程序返回单个任务,直到集合中的每个任务都已完成之后,该任务才会完成。 任务会表现为并行运行,但不会创建其他线程。 任务可以按任何顺序完成。
重要
下面的过程介绍演练:使用 Async 和 Await 访问 Web (Visual Basic) 中开发的异步应用程序的扩展。 可以通过完成演练或从 .NET 示例浏览器下载示例来开发应用程序。 示例代码位于 SerialAsyncExample 项目中。
若要运行示例,必须在计算机上安装 Visual Studio 2012 或更高版本。
向你的 GetURLContentsAsync 解决方案中添加 Task.WhenAll
向演练:使用 Async 和 Await 访问 Web (Visual Basic) 中开发的第一个应用程序添加
ProcessURLAsync
方法。如果是从开发人员代码示例下载的代码,请打开 AsyncWalkthrough 项目,然后向 MainWindow.xaml.vb 文件添加
ProcessURLAsync
。如果是通过完成演练开发的代码,请向包含
GetURLContentsAsync
方法的应用程序添加ProcessURLAsync
。 此应用程序的 MainWindow.xaml.vb 文件是“完成演练中的代码示例”部分中的第一个示例。
ProcessURLAsync
方法整合了原始演练的SumPageSizesAsync
中的For Each
循环体中的操作。 该方法以异步方式将指定网站的内容作为字节数组进行下载,然后显示并返回字节数组的长度。Private Async Function ProcessURLAsync(url As String) As Task(Of Integer) Dim byteArray = Await GetURLContentsAsync(url) DisplayResults(url, byteArray) Return byteArray.Length End Function
注释禁止或删除
SumPageSizesAsync
中的For Each
循环,如以下代码所示。'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 方法执行该查询时,它会创建下载每个网站内容的任务集合。 计算该查询时,会启动任务。
在
urlList
的声明后,将以下代码添加到方法SumPageSizesAsync
中。' 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()
向 HttpClient.GetByteArrayAsync 解决方案中添加 Task.WhenAll
向演练:使用 Async 和 Await 访问 Web (Visual Basic) 中开发的第二个应用程序添加以下版本的
ProcessURLAsync
。如果是从开发人员代码示例下载的代码,请打开 AsyncWalkthrough_HttpClient 项目,然后向 MainWindow.xaml.vb 文件添加
ProcessURLAsync
。如果是通过完成演练开发的代码,请向使用
HttpClient.GetByteArrayAsync
方法的应用程序添加ProcessURLAsync
。 此应用程序的 MainWindow.xaml.vb 文件是“完成演练中的代码示例”部分中的第二个示例。
ProcessURLAsync
方法整合了原始演练的SumPageSizesAsync
中的For Each
循环体中的操作。 该方法以异步方式将指定网站的内容作为字节数组进行下载,然后显示并返回字节数组的长度。与上面过程中的
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
注释禁止或删除
SumPageSizesAsync
中的For Each
循环,如以下代码所示。'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 方法执行该查询时,它会创建下载每个网站内容的任务集合。 计算该查询时,会启动任务。
在
client
和urlList
的声明后,将以下代码添加到方法SumPageSizesAsync
中。' 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