Cancelar as demais tarefas assíncronas depois que uma delas estiver concluída (Visual Basic)
Usando o método Task.WhenAny juntamente com um CancellationToken, você pode cancelar todas as tarefas restantes quando uma tarefa é concluída. O método WhenAny
leva um argumento que é uma coleção de tarefas. O método inicia todas as tarefas e retorna uma única tarefa. A tarefa única será concluída quando qualquer tarefa na coleção for concluída.
Este exemplo demonstra como usar um token de cancelamento em conjunto com WhenAny
para aguardar na primeira tarefa que for finalizada na coleção de tarefas e para cancelar as tarefas restantes. Cada tarefa baixa o conteúdo de um site. O exemplo exibe o comprimento do conteúdo do primeiro download que é concluído e cancela os outros downloads.
Observação
Para executar os exemplos, você precisa ter o Visual Studio 2012 ou uma versão mais recente e o .NET Framework 4.5 ou posterior instalados em seu computador.
Baixando o Exemplo
Você pode baixar o projeto completo do WPF (Windows Presentation Foundation) em Exemplo assíncrono: ajuste fino de seu aplicativo e, em seguida, seguir estas etapas.
Descompacte o arquivo baixado e, em seguida, inicie o Visual Studio.
Na barra de menus, escolha Arquivo, Abrir, Projeto/Solução.
Na caixa de diálogo Abrir Projeto, abra a pasta em que está o código de exemplo que você descompactou e, em seguida, abra o arquivo de solução (.sln) de AsyncFineTuningVB.
No Gerenciador de Soluções, abra o menu de atalho do projeto CancelAfterOneTask e, em seguida, escolha Definir como Projeto de Inicialização.
Escolha a tecla F5 para executar o projeto.
Escolha as teclas CTRL+F5 para executar o projeto sem depurá-lo.
Execute o programa várias vezes para verificar que diferentes downloads são concluídos em primeiro.
Se não quiser baixar o projeto, você poderá examinar o arquivo MainWindow.xaml.vb no final deste tópico.
Compilando o Exemplo
O exemplo neste tópico adiciona ao projeto que é desenvolvido em Cancelar uma tarefa assíncrona ou uma lista de tarefas para cancelar uma lista de tarefas. O exemplo usa a mesma interface do usuário, embora o botão Cancelar não seja explicitamente usado.
Para compilar o exemplo você mesmo, passo a passo, siga as instruções na seção "Baixando o exemplo", mas escolha CancelAListOfTasks como o Projeto de Inicialização. Adicione as alterações deste tópico ao projeto.
No arquivo MainWindow.xaml.vb do projeto CancelAListOfTasks, inicie a transição, movendo as etapas de processamento de cada site do loop em AccessTheWebAsync
para o método assíncrono seguinte.
' ***Bundle the processing steps for a website into one async method.
Async Function ProcessURLAsync(url As String, client As HttpClient, ct As CancellationToken) As Task(Of Integer)
' GetAsync returns a Task(Of HttpResponseMessage).
Dim response As HttpResponseMessage = Await client.GetAsync(url, ct)
' Retrieve the website contents from the HttpResponseMessage.
Dim urlContents As Byte() = Await response.Content.ReadAsByteArrayAsync()
Return urlContents.Length
End Function
Em AccessTheWebAsync
, este exemplo usa uma consulta, o método ToArray e o método WhenAny
para criar e iniciar uma matriz de tarefas. A aplicação de WhenAny
à matriz retorna uma única tarefa que, quando colocada em espera, resulta na primeira tarefa a alcançar a conclusão na matriz de tarefas.
Faça as seguintes alterações em AccessTheWebAsync
. Os asteriscos marcam as alterações no arquivo de código.
Comente ou exclua o loop.
Crie uma consulta que, quando executada, produz uma coleção de tarefas genéricas. Cada chamada para
ProcessURLAsync
retorna um Task<TResult> em queTResult
é um inteiro.' ***Create a query that, when executed, returns a collection of tasks. Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) = From url In urlList Select ProcessURLAsync(url, client, ct)
Chame
ToArray
para executar a consulta e iniciar as tarefas. A aplicação do métodoWhenAny
na próxima etapa executaria a consulta e iniciaria as tarefas sem usarToArray
, mas outros métodos podem não fazê-lo. A prática mais segura é forçar a execução da consulta explicitamente.' ***Use ToArray to execute the query and start the download tasks. Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
Chame
WhenAny
na coleção de tarefas.WhenAny
retorna umTask(Of Task(Of Integer))
ouTask<Task<int>>
. Ou seja,WhenAny
retorna uma tarefa que resulta em uma únicaTask(Of Integer)
ouTask<int>
quando é esperada. Essa tarefa única é a primeira tarefa na coleção a ser concluída. A tarefa que foi concluída em primeiro é atribuída afinishedTask
. O tipo definishedTask
é Task<TResult>, em queTResult
é um inteiro, porque esse é o tipo de retorno deProcessURLAsync
.' ***Call WhenAny and then await the result. The task that finishes ' first is assigned to finishedTask. Dim finishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
Neste exemplo, você está interessado apenas na tarefa que termina primeiro. Portanto, use CancellationTokenSource.Cancel para cancelar as tarefas restantes.
' ***Cancel the rest of the downloads. You just want the first one. cts.Cancel()
Por fim, aguarde que
finishedTask
recupere o comprimento do conteúdo baixado.Dim length = Await finishedTask resultsTextBox.Text &= vbCrLf & $"Length of the downloaded website: {length}" & vbCrLf
Execute o programa várias vezes para verificar que diferentes downloads são concluídos em primeiro.
Exemplo completo
O código a seguir é o arquivo MainWindow.xaml.vb ou MainWindow.xaml.cs completo para o exemplo. Os asteriscos marcam os elementos que foram adicionados para esse exemplo.
Observe que você deve adicionar uma referência para System.Net.Http.
Você pode baixar o projeto de Exemplo assíncrono: ajuste fino de seu aplicativo.
' Add an Imports directive and a reference for System.Net.Http.
Imports System.Net.Http
' Add the following Imports directive for System.Threading.
Imports System.Threading
Class MainWindow
' Declare a System.Threading.CancellationTokenSource.
Dim cts As CancellationTokenSource
Private Async Sub startButton_Click(sender As Object, e As RoutedEventArgs)
' Instantiate the CancellationTokenSource.
cts = New CancellationTokenSource()
resultsTextBox.Clear()
Try
Await AccessTheWebAsync(cts.Token)
resultsTextBox.Text &= vbCrLf & "Download complete."
Catch ex As OperationCanceledException
resultsTextBox.Text &= vbCrLf & "Download canceled." & vbCrLf
Catch ex As Exception
resultsTextBox.Text &= vbCrLf & "Download failed." & vbCrLf
End Try
' Set the CancellationTokenSource to Nothing when the download is complete.
cts = Nothing
End Sub
' You can still include a Cancel button if you want to.
Private Sub cancelButton_Click(sender As Object, e As RoutedEventArgs)
If cts IsNot Nothing Then
cts.Cancel()
End If
End Sub
' Provide a parameter for the CancellationToken.
' Change the return type to Task because the method has no return statement.
Async Function AccessTheWebAsync(ct As CancellationToken) As Task
Dim client As HttpClient = New HttpClient()
' Call SetUpURLList to make a list of web addresses.
Dim urlList As List(Of String) = SetUpURLList()
'' Comment out or delete the loop.
''For Each url In urlList
'' ' GetAsync returns a Task(Of HttpResponseMessage).
'' ' Argument ct carries the message if the Cancel button is chosen.
'' ' Note that the Cancel button can cancel all remaining downloads.
'' Dim response As HttpResponseMessage = Await client.GetAsync(url, ct)
'' ' Retrieve the website contents from the HttpResponseMessage.
'' Dim urlContents As Byte() = Await response.Content.ReadAsByteArrayAsync()
'' resultsTextBox.Text &=
'' vbCrLf & $"Length of the downloaded string: {urlContents.Length}." & vbCrLf
''Next
' ***Create a query that, when executed, returns a collection of tasks.
Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
From url In urlList Select ProcessURLAsync(url, client, ct)
' ***Use ToArray to execute the query and start the download tasks.
Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
' ***Call WhenAny and then await the result. The task that finishes
' first is assigned to finishedTask.
Dim finishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
' ***Cancel the rest of the downloads. You just want the first one.
cts.Cancel()
' ***Await the first completed task and display the results
' Run the program several times to demonstrate that different
' websites can finish first.
Dim length = Await finishedTask
resultsTextBox.Text &= vbCrLf & $"Length of the downloaded website: {length}" & vbCrLf
End Function
' ***Bundle the processing steps for a website into one async method.
Async Function ProcessURLAsync(url As String, client As HttpClient, ct As CancellationToken) As Task(Of Integer)
' GetAsync returns a Task(Of HttpResponseMessage).
Dim response As HttpResponseMessage = Await client.GetAsync(url, ct)
' Retrieve the website contents from the HttpResponseMessage.
Dim urlContents As Byte() = Await response.Content.ReadAsByteArrayAsync()
Return urlContents.Length
End Function
' Add a method that creates a list of web addresses.
Private Function SetUpURLList() As List(Of String)
Dim urls = New List(Of String) From
{
"https://msdn.microsoft.com",
"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
End Class
' Sample output:
' Length of the downloaded website: 158856
' Download complete.