Porady: rozszerzanie przewodnika asynchronicznego przy użyciu elementu Task.WhenAll (Visual Basic)

Wydajność rozwiązania asynchronicznego można poprawić w przewodniku: uzyskiwanie dostępu do sieci Web przy użyciu Async i Await (Visual Basic) przy użyciu Task.WhenAll metody . Ta metoda asynchronicznie oczekuje na wiele operacji asynchronicznych, które są reprezentowane jako kolekcja zadań.

Możesz zauważyć w przewodniku, że witryny internetowe pobierają się z różnych stawek. Czasami jedna z witryn internetowych jest bardzo niska, co opóźnia wszystkie pozostałe pliki do pobrania. Po uruchomieniu rozwiązań asynchronicznych, które tworzysz w przewodniku, możesz łatwo zakończyć program, jeśli nie chcesz czekać, ale lepszym rozwiązaniem byłoby uruchomienie wszystkich pobrań w tym samym czasie i pozwolić na kontynuowanie szybszego pobierania bez oczekiwania na opóźnienie.

Metoda jest Task.WhenAll stosowana do kolekcji zadań. Aplikacja zwraca WhenAll pojedyncze zadanie, które nie zostanie ukończone do momentu ukończenia każdego zadania w kolekcji. Zadania wydają się działać równolegle, ale nie są tworzone żadne dodatkowe wątki. Zadania mogą być wykonywane w dowolnej kolejności.

Ważne

Poniższe procedury opisują rozszerzenia aplikacji asynchronicznych, które są opracowywane w przewodniku: Uzyskiwanie dostępu do sieci Web przy użyciu Async i Await (Visual Basic). Aplikacje można opracowywać, wykonując przewodnik lub pobierając przykład z przeglądarki przykładowej platformy .NET. Przykładowy kod znajduje się w projekcie SerialAsyncExample .

Aby uruchomić przykład, na komputerze musi być zainstalowany program Visual Studio 2012 lub nowszy.

Aby dodać element Task.WhenAll do rozwiązania GetURLContentsAsync

  1. Dodaj metodę ProcessURLAsync do pierwszej aplikacji opracowanej w temacie Walkthrough: Accessing the Web by Using Async and Await (Visual Basic)( Uzyskiwanie dostępu do sieci Web przy użyciu Async i Await (Visual Basic).

    • Jeśli kod został pobrany z przykładów kodu dla deweloperów, otwórz projekt AsyncWalkthrough, a następnie dodaj ProcessURLAsync go do pliku MainWindow.xaml.vb.

    • Jeśli kod został opracowany przez ukończenie przewodnika, dodaj ProcessURLAsync go do aplikacji zawierającej metodę GetURLContentsAsync . Plik MainWindow.xaml.vb dla tej aplikacji jest pierwszym przykładem w sekcji "Complete Code Examples from the Walkthrough" (Kompletne przykłady kodu z przewodnika).

    Metoda ProcessURLAsync konsoliduje akcje w treści For Each pętli w SumPageSizesAsync oryginalnym przewodniku. Metoda asynchronicznie pobiera zawartość określonej witryny internetowej jako tablicę bajtów, a następnie wyświetla i zwraca długość tablicy bajtów.

    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. Oznacz jako komentarz lub usuń pętlę For Each w pliku SumPageSizesAsync, jak pokazano w poniższym kodzie.

    '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. Utwórz kolekcję zadań. Poniższy kod definiuje zapytanie , które po wykonaniu ToArray przez metodę tworzy kolekcję zadań pobierających zawartość każdej witryny internetowej. Zadania są uruchamiane po ocenie zapytania.

    Dodaj następujący kod do metody SumPageSizesAsync po deklaracji 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. Zastosuj Task.WhenAll do kolekcji zadań, downloadTasks. Task.WhenAll Zwraca pojedyncze zadanie, które kończy się po zakończeniu wszystkich zadań w kolekcji zadań.

    W poniższym przykładzie Await wyrażenie oczekuje na ukończenie pojedynczego zadania, które WhenAll zwraca. Wyrażenie oblicza tablicę liczb całkowitych, gdzie każda liczba całkowita to długość pobranej witryny internetowej. Dodaj następujący kod do SumPageSizesAsyncmetody tuż po kodzie dodanym w poprzednim kroku.

    ' 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. Na koniec użyj Sum metody , aby obliczyć sumę długości wszystkich witryn internetowych. Dodaj następujący wiersz do SumPageSizesAsync.

    Dim total = lengths.Sum()
    

Aby dodać element Task.WhenAll do rozwiązania HttpClient.GetByteArrayAsync

  1. Dodaj następującą wersję ProcessURLAsync do drugiej aplikacji opracowanej w przewodniku: Uzyskiwanie dostępu do sieci Web przy użyciu narzędzia Async i Await (Visual Basic).

    • Jeśli kod został pobrany z przykładów kodu dla deweloperów, otwórz projekt AsyncWalkthrough_HttpClient, a następnie dodaj ProcessURLAsync go do pliku MainWindow.xaml.vb.

    • Jeśli kod został opracowany przez ukończenie przewodnika, dodaj ProcessURLAsync go do aplikacji korzystającej z HttpClient.GetByteArrayAsync metody . Plik MainWindow.xaml.vb dla tej aplikacji jest drugim przykładem w sekcji "Complete Code Examples from the Walkthrough" (Pełne przykłady kodu z przewodnika).

    Metoda ProcessURLAsync konsoliduje akcje w treści For Each pętli w SumPageSizesAsync oryginalnym przewodniku. Metoda asynchronicznie pobiera zawartość określonej witryny internetowej jako tablicę bajtów, a następnie wyświetla i zwraca długość tablicy bajtów.

    Jedyną różnicą od ProcessURLAsync metody w poprzedniej procedurze jest użycie HttpClient wystąpienia . 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. Oznacz jako komentarz lub usuń pętlę For Each w pliku SumPageSizesAsync, jak pokazano w poniższym kodzie.

    '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. Zdefiniuj zapytanie , które po wykonaniu ToArray przez metodę tworzy kolekcję zadań pobierających zawartość każdej witryny internetowej. Zadania są uruchamiane po ocenie zapytania.

    Dodaj następujący kod do metody SumPageSizesAsync po deklaracji client i 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. Następnie zastosuj się Task.WhenAll do kolekcji zadań podrzędnych. downloadTasks Task.WhenAll Zwraca pojedyncze zadanie, które kończy się po zakończeniu wszystkich zadań w kolekcji zadań.

    W poniższym przykładzie Await wyrażenie oczekuje na ukończenie pojedynczego zadania, które WhenAll zwraca. Po zakończeniu Await wyrażenie oblicza tablicę liczb całkowitych, gdzie każda liczba całkowita to długość pobranej witryny internetowej. Dodaj następujący kod do SumPageSizesAsyncmetody tuż po kodzie dodanym w poprzednim kroku.

    ' 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. Na koniec użyj Sum metody , aby uzyskać sumę długości wszystkich witryn internetowych. Dodaj następujący wiersz do SumPageSizesAsync.

    Dim total = lengths.Sum()
    

Aby przetestować rozwiązania Task.WhenAll

W przypadku dowolnego rozwiązania wybierz klawisz F5, aby uruchomić program, a następnie wybierz przycisk Uruchom . Dane wyjściowe powinny przypominać dane wyjściowe z rozwiązań asynchronicznych w przewodniku: uzyskiwanie dostępu do sieci Web przy użyciu Async i Await (Visual Basic). Należy jednak zauważyć, że witryny internetowe są wyświetlane w innej kolejności za każdym razem.

Przykład 1

Poniższy kod przedstawia rozszerzenia projektu, które używają GetURLContentsAsync metody do pobierania zawartości z internetu.

' 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

Przykład 2

Poniższy kod przedstawia rozszerzenia projektu, które używają metody HttpClient.GetByteArrayAsync do pobierania zawartości z sieci 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

Zobacz też