您可以在 逐步解說:使用 Async 和 Await 存取 Web 中,透過使用 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建立任務的清單。 下列程式代碼定義了一個查詢,當該查詢由方法執行時,會建立一組工作來下載每個網站的內容。 系統會在評估查詢時啟動工作。
在 宣告
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()
若要在 HttpClient.GetByteArrayAsync 解決方案中新增 Task.WhenAll
請將以下版本的
ProcessURLAsync新增至使用於逐步解說——《使用 Async 和 Await 存取 Web(Visual Basic)》中開發的第二個應用程式。如果您從 開發人員程式代碼範例下載程式代碼,請開啟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定義查詢,當由方法執行時,建立一個工作集合,以下載每個網站的內容。 系統會在評估查詢時啟動工作。
在宣告
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至工作集合。downloadTasksTask.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擴充功能,該專案使用GetURLContentsAsync方法從網路下載內容。
' 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