Iniciar várias tarefas assíncronas e processá-las na conclusão (C# e Visual Basic)

Ao usar Task.WhenAny, você pode iniciar várias tarefas ao mesmo tempo e processá-las uma a uma a medida que são concluídas em vez de processá-las na ordem em que foram iniciadas.

O exemplo a seguir usa uma consulta para criar uma coleção de tarefas. Cada tarefa baixa o conteúdo de um site especificada. Em cada iteração de um loop while, uma chamada aguardada feita a WhenAny retorna a tarefa na coleção de tarefas que termina seu download primeiro. Essa tarefa é removida da coleção e processada. O loop é repetido até que a coleção não contenha mais nenhuma tarefa.

Dica

Para executar os exemplos, você deve ter o Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 for Windows Desktop, Visual Studio Express 2013 para Windows, ou o .NET Framework 4.5 ou 4.5.1 instalado no seu computador.

Baixando o Exemplo

Você pode baixar projeto completo do Windows Presentation Foundation (WPF) de Exemplo de Async: Ajustando seu aplicativo.

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

  2. Na barra de menu, 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 AsyncFineTuningCS ou AsyncFineTuningVB.

  4. Em Gerenciador de Soluções, abra o menu de atalho para o projeto ProcessTasksAsTheyFinish e escolha Definir como Projeto de Inicialização.

  5. Escolha a tecla F5 para executar o projeto.

    Escolha as chaves Ctrl+F5 para executar o projeto sem depurá-las.

  6. Execute o projeto várias vezes para verificar se os tamanhos baixados nem sempre aparecem na mesma ordem em cada vez.

Se você não quiser baixar o projeto, você pode examinar os arquivos de MainWindow.xaml.vb e de MainWindow.xaml.cs no final deste tópico.

Compilando o Exemplo

Este exemplo adiciona o código que é desenvolvido no Cancelar as demais tarefas assíncronas depois que uma delas estiver concluída (C# e Visual Basic) e usa a mesma interface do usuário.

Para criar o exemplo você mesmo, passo a passo, siga as instruções na seção "Baixando o exemplo", mas selecione CancelAfterOneTask como Projeto de inicialização. Adicione as alterações neste tópico para o método AccessTheWebAsync no projeto. As alterações são marcadas com asteriscos.

O projeto de CancelAfterOneTask já inclui uma consulta que, quando executada, cria uma coleção de tarefas. Cada chamada para ProcessURLAsync no código a seguir retorna Task onde TResult que é um inteiro.

Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
    From url In urlList Select ProcessURLAsync(url, client, ct)
IEnumerable<Task<int>> downloadTasksQuery =
    from url in urlList select ProcessURL(url, client, ct);

No arquivo MainWindow.xaml.vb ou MainWindow.xaml.cs do projeto, faça as seguintes alterações no método AccessTheWebAsync.

  • Executar a consulta aplicando Enumerable.ToList``1 em vez de ToArray``1.

    Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()
    
    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
    
  • Adicione um loop enquanto executa as seguintes etapas para cada tarefa na coleção.

    1. Aguarda uma chamada a WhenAny para identificar a primeira tarefa na coleção para concluir o download.

      Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
      
      Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
      
    2. Remove essa tarefa de coleção.

      downloadTasks.Remove(firstFinishedTask)
      
      downloadTasks.Remove(firstFinishedTask);
      
    3. Aguarda firstFinishedTask, que é retornado por uma chamada a ProcessURLAsync. A variável de firstFinishedTask é Task onde TReturn é um número inteiro. A tarefa já está concluída, mas você espera para recuperar o comprimento do site baixado, como mostra o exemplo a seguir.

      Dim length = Await firstFinishedTask
      resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website:  {0}" & vbCrLf, length)
      
      int length = await firstFinishedTask;
      resultsTextBox.Text += String.Format("\r\nLength of the download:  {0}", length);
      

É necessário executar o projeto várias vezes para verificar se os tamanhos baixados não aparecem sempre na mesma ordem.

Aviso

Você pode usar WhenAny em um loop, conforme descrito no exemplo, para resolver problemas que envolvam um pequeno número de tarefas.No entanto, outras abordagens são mais eficientes se você tem um grande número de tarefas para processar.Para obter mais informações e exemplos, consulte Processando tarefas à medida que são concluídas.

Exemplo completo

O código a seguir é o texto completo de MainWindow.xaml.vb ou de MainWindow.xaml.cs do 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 de Async: Ajustando 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 & "Downloads complete." 

        Catch ex As OperationCanceledException
            resultsTextBox.Text &= vbCrLf & "Downloads canceled." & vbCrLf

        Catch ex As Exception
            resultsTextBox.Text &= vbCrLf & "Downloads 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()

        ' ***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 ToList to execute the query and start the download tasks.  
        Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()

        ' ***Add a loop to process the tasks one at a time until none remain. 
        While downloadTasks.Count > 0
            ' ***Identify the first task that completes. 
            Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)

            ' ***Remove the selected task from the list so that you don't 
            ' process it more than once.
            downloadTasks.Remove(firstFinishedTask)

            ' ***Await the first completed task and display the results. 
            Dim length = Await firstFinishedTask
            resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website:  {0}" & vbCrLf, length)
        End While 

    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/en-us/library/hh290138.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            }
        Return urls
    End Function 

End Class 


' Sample output: 

' Length of the download:  226093 
' Length of the download:  412588 
' Length of the download:  175490 
' Length of the download:  204890 
' Length of the download:  158855 
' Length of the download:  145790 
' Length of the download:  44908 
' Downloads complete.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

// Add a using directive and a reference for System.Net.Http. 
using System.Net.Http;

// Add the following using directive. 
using System.Threading;


namespace ProcessTasksAsTheyFinish
{
    public partial class MainWindow : Window
    {
        // Declare a System.Threading.CancellationTokenSource.
        CancellationTokenSource cts;

        public MainWindow()
        {
            InitializeComponent();
        }

        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            resultsTextBox.Clear();

            // Instantiate the CancellationTokenSource.
            cts = new CancellationTokenSource();

            try
            {
                await AccessTheWebAsync(cts.Token);
                resultsTextBox.Text += "\r\nDownloads complete.";
            }
            catch (OperationCanceledException)
            {
                resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
            }
            catch (Exception)
            {
                resultsTextBox.Text += "\r\nDownloads failed.\r\n";
            }

            cts = null;
        }


        private void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            if (cts != null)
            {
                cts.Cancel();
            }
        }


        async Task AccessTheWebAsync(CancellationToken ct)
        {
            HttpClient client = new HttpClient();

            // Make a list of web addresses.
            List<string> urlList = SetUpURLList();

            // ***Create a query that, when executed, returns a collection of tasks.
            IEnumerable<Task<int>> downloadTasksQuery =
                from url in urlList select ProcessURL(url, client, ct);

            // ***Use ToList to execute the query and start the tasks. 
            List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

            // ***Add a loop to process the tasks one at a time until none remain. 
            while (downloadTasks.Count > 0)
            {
                    // Identify the first task that completes.
                    Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);

                    // ***Remove the selected task from the list so that you don't 
                    // process it more than once.
                    downloadTasks.Remove(firstFinishedTask);

                    // Await the completed task. 
                    int length = await firstFinishedTask;
                    resultsTextBox.Text += String.Format("\r\nLength of the download:  {0}", length);
            }
        }


        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "https://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }


        async Task<int> ProcessURL(string url, HttpClient client, CancellationToken ct)
        {
            // GetAsync returns a Task<HttpResponseMessage>. 
            HttpResponseMessage response = await client.GetAsync(url, ct);

            // Retrieve the website contents from the HttpResponseMessage. 
            byte[] urlContents = await response.Content.ReadAsByteArrayAsync();

            return urlContents.Length;
        }
    }
}

// Sample Output: 

// Length of the download:  226093 
// Length of the download:  412588 
// Length of the download:  175490 
// Length of the download:  204890 
// Length of the download:  158855 
// Length of the download:  145790 
// Length of the download:  44908 
// Downloads complete.

Consulte também

Referência

WhenAny``1

Conceitos

Ajustando seu aplicativo Async (C# e Visual Basic)

Programação assíncrona com Async e Await (C# e Visual Basic)

Outros recursos

Exemplo de Async: Ajustando o aplicativo