Iniciar varias tareas asincrónicas y procesarlas a medida que se completan (C# y Visual Basic)

Mediante Task.WhenAny, se pueden iniciar tareas simultáneamente y procesarlas una por una a medida que se completan en lugar de procesarlas en el orden en el que se inician.

El ejemplo siguiente utiliza una consulta para crear una colección de tareas. Cada tarea descarga el contenido de un sitio web especificado. En cada iteración de un bucle while, una llamada esperada a WhenAny devuelve la tarea en la colección de tareas que finaliza primero su descarga. Esa tarea se elimina de la colección y es procesada. El bucle se repite hasta que la colección no contenga más tareas.

Nota

Para ejecutar los ejemplos, debe tener Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 para escritorio de Windows, Visual Studio Express 2013 para Windows o .NET Framework 4.5 o 4.5.1 instalado en su equipo.

Descargar el ejemplo

Puede descargar el proyecto completado de Windows Presentation Foundation (WPF) en Ejemplo Async: Ajuste de la aplicación y seguir estos pasos.

  1. Descomprima el archivo que ha descargado y, a continuación, inicie Visual Studio.

  2. En la barra de menú, elija Archivo, Abrir, Proyecto/Solución.

  3. En el cuadro Abrir proyecto, abra la carpeta que contiene código de ejemplo que descomprimió y, a continuación, abra el archivo de solución (.sln) para AsyncFineTuningCS o AsyncFineTuningVB.

  4. En el Explorador de soluciones, abra el acceso directo del proyecto de ProcessTasksAsTheyFinish y, a continuación, elija Establecer como proyecto de inicio.

  5. Elija la tecla F5 para ejecutar el proyecto.

    Elija las teclas CTRL+F5 para ejecutar el proyecto sin depurarlo.

  6. Ejecute el proyecto varias veces para verificar que las longitudes descargadas no siempre aparecen en el mismo orden.

Si no desea descargar el proyecto, puede revisar los archivos completos de MainWindow.xaml.vb y de MainWindow.xaml.cs al final de este tema.

Compilar el ejemplo

Este ejemplo se agrega al código que se desarrolla en Cancelar las tareas asincrónicas restantes cuando se completa una (C# y Visual Basic) y utiliza la misma interfaz de usuario.

Para compilar el ejemplo usted mismo, paso a paso, siga las instrucciones de la sección “Descargar el ejemplo” pero elija CancelAfterOneTask como el Proyecto de inicio. Agregue los cambios de este tema al método AccessTheWebAsync de dicho proyecto. Los cambios se marcan con asteriscos.

El proyecto CancelAfterOneTask ya incluye una consulta que, cuando se ejecuta, crea una colección de tareas. Cada llamada a ProcessURLAsync en el código siguiente devuelve Task donde TResult es un entero.

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);

En el archivo MainWindow.xaml.vb o MainWindow.xaml.cs del proyecto, realice los cambios siguientes en el método AccessTheWebAsync.

  • Ejecute la consulta aplicando Enumerable.ToList``1 en lugar de ToArray``1.

    Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()
    
    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
    
  • Agregue un bucle while que realiza los pasos siguientes para cada tarea en la colección.

    1. Espera una llamada a WhenAny para identificar la primera tarea en la colección para terminar su descarga.

      Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
      
      Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
      
    2. Elimina la tarea de la colección.

      downloadTasks.Remove(firstFinishedTask)
      
      downloadTasks.Remove(firstFinishedTask);
      
    3. Espera firstFinishedTask, que es devuelta por una llamada a ProcessURLAsync. La variable firstFinishedTask es un objeto Task donde TReturn es un entero. La tarea ya está completa, pero se espera que obtenga la longitud del sitio web descargado, como muestra el siguiente ejemplo.

      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);
      

Debe ejecutar el proyecto varias veces para verificar que las longitudes descargadas no siempre aparecen en el mismo orden.

Advertencia

Puede utilizar WhenAny en un bucle, tal como se describe en el ejemplo, para resolver problemas que afectan a un número de tareas reducido.Sin embargo, otros enfoques son más eficientes si tiene un gran número de tareas para procesar.Para obtener más información y ejemplos, vea Tareas de procesamiento como completa.

Ejemplo completo

El código siguiente es el texto completo del archivo MainWindow.xaml.vb o MainWindow.xaml.cs para el ejemplo. Los asteriscos marcan los elementos agregados para este ejemplo.

Observe que debe agregar una referencia para System.Net.Http.

Puede descargar el proyecto desde Ejemplo Async: Ajuste de la aplicación.

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

Vea también

Referencia

WhenAny``1

Conceptos

Ajustar una aplicación asincrónica (C# y Visual Basic)

Programación asincrónica con Async y Await (C# y Visual Basic)

Otros recursos

Ejemplo Async: Ajuste de la aplicación