如何:使用 Task.WhenAll 扩展异步演示 (Visual Basic)

演练:使用 Async 和 Await 访问 Web(Visual Basic)中,可以通过使用Task.WhenAll方法来改进异步解决方案的性能。 此方法异步等待多个异步操作,这些操作被表示为任务集合。

你可能已在演练中注意到网站以不同速率进行下载。 有时其中一个网站速度非常慢,这会延迟所有剩余的下载。 在演练中运行生成的异步解决方案时,如果不想等待,则可以轻松结束程序,但更好的选择是同时启动所有下载,让更快的下载继续,而无需等待延迟的下载。

可将 Task.WhenAll 方法应用于任务的集合。 WhenAll 的应用程序返回单个任务,直到集合中的每个任务都已完成之后,该任务才会完成。 任务似乎并行运行,但不会创建其他线程。 任务可以按任意顺序完成。

重要

下面的过程介绍演练:使用 Async 和 Await 访问 Web (Visual Basic) 中开发的异步应用程序的扩展。 可以通过完成演练或从 .NET 示例浏览器下载示例来开发应用程序。 示例代码位于 SerialAsyncExample 项目中。

若要运行该示例,必须在计算机上安装 Visual Studio 2012 或更高版本。

将 Task.WhenAll 添加到 GetURLContentsAsync 解决方案

  1. 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
    
  2. 注释禁止或删除 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
    
  3. 创建任务集合。 以下代码定义了一个 查询 ,该方法执行 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()
    
  4. Task.WhenAll应用于任务集合downloadTasksTask.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
    
  5. 最后,使用 Sum 该方法计算所有网站的长度之和。 将以下行添加到 SumPageSizesAsync

    Dim total = lengths.Sum()
    

将 Task.WhenAll 添加到 HttpClient.GetByteArrayAsync 解决方案

  1. 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
    
  2. 注释禁止或删除 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
    
  3. 定义 一个查询 ,该方法执行 ToArray 时,会创建下载每个网站内容的任务集合。 查询被评估时,任务将被启动。

    在声明SumPageSizesAsyncclient之后,将以下代码添加到方法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()
    
  4. 接下来,将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
    
  5. 最后,使用 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

另请参阅