Compartilhar via


Cancelar tarefas assíncronas restantes após a conclusão de uma (Visual Basic)

Usando o Task.WhenAny método junto com um CancellationToken, você pode cancelar todas as tarefas restantes quando uma tarefa for concluída. O WhenAny método usa um argumento que é uma coleção de tarefas. O método inicia todas as tarefas e retorna uma única tarefa. A tarefa única é considerada como concluída quando uma tarefa qualquer na coleção é concluída.

Este exemplo demonstra como usar um token de cancelamento em conjunto com WhenAny para segurar a primeira tarefa a ser concluída de uma coleção de tarefas e cancelar as tarefas restantes. Cada tarefa baixa o conteúdo de um site. O exemplo exibe o comprimento do conteúdo do primeiro download a ser concluído e cancela os outros downloads.

Observação

Para executar os exemplos, você deve ter o Visual Studio 2012 ou mais recente e o .NET Framework 4.5 ou mais recente instalado em seu computador.

Baixando o exemplo

Você pode baixar o projeto completo do Windows Presentation Foundation (WPF) de Exemplo assíncrono: ajustar seu aplicativo e seguir estas etapas.

  1. Descompacte o arquivo que você baixou e inicie o Visual Studio.

  2. Na barra de menus, escolha Arquivo, Abrir, Projeto/Solução.

  3. Na caixa de diálogo Abrir Projeto , abra a pasta que contém o código de exemplo que você descompactou e abra o arquivo de solução (.sln) para AsyncFineTuningVB.

  4. No Gerenciador de Soluções, abra o menu de atalho do projeto CancelAfterOneTask e escolha Definir como Projeto de Inicialização.

  5. Escolha a chave F5 para executar o projeto.

    Escolha as teclas Ctrl+F5 para executar o projeto sem depurá-lo.

  6. Execute o programa várias vezes para verificar se os downloads diferentes terminam primeiro.

Se você não quiser baixar o projeto, poderá examinar o arquivo MainWindow.xaml.vb no final deste tópico.

Criando o exemplo

O exemplo neste tópico adiciona ao projeto 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 usado explicitamente.

Para criar o exemplo por conta própria, siga as instruções na seção "Baixando o exemplo", mas escolha CancelAListOfTasks como o Projeto de Inicialização. Adicione as alterações neste tópico a esse projeto.

No arquivo MainWindow.xaml.vb do projeto CancelAListOfTasks , inicie a transição movendo as etapas de processamento para cada site do loop AccessTheWebAsync para o método assíncrono a seguir.

' ***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

Neste AccessTheWebAsyncexemplo, este exemplo usa uma consulta, o ToArray método e o WhenAny método 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.

  1. Comente ou exclua o loop.

  2. Crie uma consulta que, quando executada, produz uma coleção de tarefas genéricas. Cada chamada para ProcessURLAsync retorna um Task<TResult> onde TResult é 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)
    
  3. Chame ToArray para executar a consulta e inicie as tarefas. A aplicação do WhenAny método na próxima etapa executaria a consulta e iniciaria as tarefas sem usar ToArray, mas outros métodos podem não. 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()
    
  4. Chame WhenAny na coleção de tarefas. WhenAny retorna um Task(Of Task(Of Integer)) ou Task<Task<int>>. Ou seja, WhenAny retorna uma tarefa que resulta em uma única Task(Of Integer) ou Task<int> quando é esperada. Essa única tarefa é a primeira tarefa da coleção a ser concluída. A tarefa que terminou primeiro é atribuída a finishedTask. O tipo de finishedTask é Task<TResult> onde TResult é um inteiro porque esse é o tipo de retorno de ProcessURLAsync.

    ' ***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)
    
  5. 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()
    
  6. Por fim, aguarde finishedTask para recuperar 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 se os downloads diferentes terminam 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 este exemplo.

Observe que você deve adicionar uma referência para System.Net.Http.

Você pode baixar o projeto no link Exemplo Assíncrono: Otimização do 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.

Consulte também