Como estender as instruções passo a passo assíncronas usando Task.WhenAll (Visual Basic)
Você pode melhorar o desempenho da solução assíncrona em Passo a passo: acessando a Web Usando async e await (Visual Basic), usando o método Task.WhenAll. Esse método aguarda de maneira assíncrona várias operações assíncronas, que são representadas como uma coleção de tarefas.
Você deve ter notado no passo a passo que os sites fazem o download em taxas diferentes. Às vezes, um dos sites está muito lento e isso atrasa todos os downloads restantes. Ao executar as soluções assíncronas que você compilou no passo a passo, você poderá finalizar o programa facilmente se não quiser esperar, mas uma opção melhor seria iniciar todos os downloads ao mesmo tempo e permitir que os downloads mais rápidos continuem, sem aguardar o que está atrasado.
Você aplica o método Task.WhenAll
a uma coleção de tarefas. A aplicação de WhenAll
retorna uma única tarefa que não será concluída até a conclusão de cada tarefa na coleção. As tarefas parecem ser executadas em paralelo, mas não são criados threads adicionais. As tarefas podem ser concluídas em qualquer ordem.
Importante
Os procedimentos a seguir descrevem as extensões para os aplicativos assíncronos que são desenvolvidos em Passo a passo: acessando a Web Usando Async e Await (Visual Basic). Você pode desenvolver os aplicativos concluindo o passo a passo ou baixando a amostra em Navegador de Amostras do .NET. O código de exemplo está no projeto SerialAsyncExample.
Para executar o exemplo, você deve ter o Visual Studio 2012 ou mais recente instalado no seu computador.
Para adicionar o Task.WhenAll à sua solução GetURLContentsAsync
Adicione o método
ProcessURLAsync
ao primeiro aplicativo que é desenvolvido em Passo a passo: acessando a Web Usando Async e Await (Visual Basic).Se você baixou o código de Exemplos de Código para Desenvolvedores, abra o projeto AsyncWalkthrough e, em seguida, adicione
ProcessURLAsync
ao arquivo MainWindow.xaml.vb.Se você desenvolveu o código ao concluir o passo a passo, adicione
ProcessURLAsync
ao aplicativo que inclui o métodoGetURLContentsAsync
. O arquivo MainWindow.xaml.vb para este aplicativo é o primeiro exemplo da seção "Exemplos de Código Completos do Passo a passo".
O método
ProcessURLAsync
consolida as ações no corpo do loopFor Each
emSumPageSizesAsync
no passo a passo original. O método baixa de maneira assíncrona o conteúdo de um site especificado como uma matriz de bytes e, em seguida, exibe e retorna o comprimento da matriz de bytes.Private Async Function ProcessURLAsync(url As String) As Task(Of Integer) Dim byteArray = Await GetURLContentsAsync(url) DisplayResults(url, byteArray) Return byteArray.Length End Function
Comente ou exclua o loop
For Each
emSumPageSizesAsync
, como mostrado no código a seguir.'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
Crie uma coleção de tarefas. O código a seguir define uma consulta que, quando executada pelo método ToArray, cria uma coleção de tarefas que baixa o conteúdo de cada site. As tarefas são iniciadas quando a consulta é avaliada.
Adicione o seguinte código ao método
SumPageSizesAsync
depois da declaração daurlList
.' 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()
Aplique
Task.WhenAll
à coleção de tarefasdownloadTasks
. OTask.WhenAll
retorna uma única tarefa que será terminada quando todas as tarefas na coleção de tarefas forem concluídas.No exemplo a seguir, a expressão
Await
aguarda a conclusão da única tarefa que oWhenAll
retorna. A expressão é avaliada para uma matriz de inteiros, em que cada inteiro é o comprimento de um site baixado. Adicione o seguinte código aoSumPageSizesAsync
, logo após o código que você adicionou na etapa anterior.' 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
Por fim, use o método Sum para calcular a soma dos comprimentos de todos os sites. Adicione a seguinte linha ao
SumPageSizesAsync
.Dim total = lengths.Sum()
Para adicionar o Task.WhenAll à solução HttpClient.GetByteArrayAsync
Adicione a seguinte versão de
ProcessURLAsync
ao segundo aplicativo que é desenvolvido em Passo a passo: Acessando a Web usando Async e Await (Visual Basic).Se você baixou o código de Exemplos de Código para Desenvolvedores, abra o projeto AsyncWalkthrough_HttpClient e, em seguida, adicione
ProcessURLAsync
ao arquivo MainWindow.xaml.vb.Se você desenvolveu o código ao concluir o passo a passo, adicione
ProcessURLAsync
ao aplicativo que usa o métodoHttpClient.GetByteArrayAsync
. O arquivo MainWindow.xaml.vb para este aplicativo é o segundo exemplo da seção "Exemplos de Código Completos do Passo a passo".
O método
ProcessURLAsync
consolida as ações no corpo do loopFor Each
emSumPageSizesAsync
no passo a passo original. O método baixa de maneira assíncrona o conteúdo de um site especificado como uma matriz de bytes e, em seguida, exibe e retorna o comprimento da matriz de bytes.A única diferença do método
ProcessURLAsync
no procedimento anterior é o uso da instância HttpClient, aclient
.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
Comente ou exclua o loop
For Each
emSumPageSizesAsync
, como mostrado no código a seguir.'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
Defina uma consulta que, quando executada pelo método ToArray, cria uma coleção de tarefas que baixa o conteúdo de cada site. As tarefas são iniciadas quando a consulta é avaliada.
Adicione o seguinte código ao método
SumPageSizesAsync
depois da declaração declient
eurlList
.' 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()
Em seguida, aplique
Task.WhenAll
à coleção de tarefasdownloadTasks
. OTask.WhenAll
retorna uma única tarefa que será terminada quando todas as tarefas na coleção de tarefas forem concluídas.No exemplo a seguir, a expressão
Await
aguarda a conclusão da única tarefa que oWhenAll
retorna. Quando concluída, a expressãoAwait
é avaliada para uma matriz de inteiros, em que cada inteiro é o comprimento de um site baixado. Adicione o seguinte código aoSumPageSizesAsync
, logo após o código que você adicionou na etapa anterior.' 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
Por fim, use o método Sum para obter a soma dos comprimentos de todos os sites. Adicione a seguinte linha ao
SumPageSizesAsync
.Dim total = lengths.Sum()
Para testar as soluções Task.WhenAll
Para qualquer uma das soluções, escolha a tecla F5 para executar o programa e, em seguida, escolha o botão Iniciar. A saída deve ser parecida com a saída das soluções assíncronas em Passo a passo: acessando a Web Usando Async e Await (Visual Basic). No entanto, observe que os sites aparecem em uma ordem diferente a cada vez.
Exemplo 1
O código a seguir mostra as extensões para o projeto que usa o método GetURLContentsAsync
para baixar conteúdo da 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
Exemplo 2
O código a seguir mostra as extensões para o projeto que usa o método HttpClient.GetByteArrayAsync
para baixar conteúdo da 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