Запустить несколько задач и обрабатывать их по мере завершения (C# и Visual Basic)

С помощью Task.WhenAny можно запускать несколько задач одновременно и обрабатывать их по одному по мере их завершения, а не обрабатывать их в порядке их запуска.

В следующем примере используется запрос для создания коллекции задач.Каждая задача загружает содержимое конкретного веб-сайта.В каждой итерации цикла while ожидаемый вызов WhenAny возвращает задачу из коллекции задач, которая первой завершает свою загрузку.Эта задача удаляется из коллекции и обрабатывается.Цикл выполняется до тех пор, пока в коллекции еще есть задачи.

ПримечаниеПримечание

Чтобы выполнить примеры, необходимо иметь Visual Studio 2012, Visual Studio Express 2012 для рабочего стола Windows или .NET Framework 4,5, на компьютере.

Загрузить пример

Можно загрузить всего проекта Windows Presentation Foundation (WPF), Пример Async. Настраивать приложение и затем выполните следующие действия.

  1. Распакуйте файл, который загружен, затем запустите Visual Studio 2012.

  2. В строке меню выберите Файл, Открыть, Проект/Решение.

  3. В диалоговом окне Открыть проект откройте папку, которая содержит пример кода, который распаковали, а затем открывает файл решения (SLN) для AsyncFineTuningCS или AsyncFineTuningVB.

  4. В Обозревателе решений откройте контекстное меню для проекта ProcessTasksAsTheyFinish, а затем выберите Назначить автозапускаемым проектом.

  5. Выберите ключ F5 для запуска проекта.

    Выберите ключи Ctrl+F5, чтобы запустить проект без отладки.

  6. Запустите проект несколько раз проверить, загруженные длины не всегда отображаются в том же порядке.

Если не требуется загрузить проекта можно просмотреть файлы MainWindow.xaml.vb или MainWindow.xaml.cs в конце этого раздела.

Построение примера

Код из этого примера добавляется к коду, разработанному в Отменить оставшиеся задачи после завершения одной из них (C# и Visual Basic), и использует один и тот же пользовательский интерфейс.

Для построения примера самостоятельно пошаговый, следуйте инструкциям в разделе "загрузка примере", но выберите CancelAfterOneTask в качестве Начальный проект.Добавьте изменений в данном разделе методу AccessTheWebAsync в проекте.Изменения отмечены звездочками.

Проект CancelAfterOneTask уже содержит запрос, который выполняется, создает коллекцию задач.Каждый вызов ProcessURLAsync в следующем коде возвращает Task<TResult>, где TResult — целое число.

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

В файле MainWindow.xaml.vb или MainWindow.xaml.cs проекта, внесите следующие изменения в метод AccessTheWebAsync.

  • Выполнение запроса путем применения атрибута Enumerable.ToList<TSource> вместо ToArray<TSource>.

    Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()
    
    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
    
  • Добавьте цикл while, выполняет следующие шаги для каждой задачи в коллекции.

    1. Ожидает вызов WhenAny для определения первой задачи из коллекции, которая завершит свою загрузку.

      Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
      
      Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
      
    2. Удаляет такую задачу из коллекции.

      downloadTasks.Remove(firstFinishedTask)
      
      downloadTasks.Remove(firstFinishedTask);
      
    3. Ожидает firstFinishedTask, возвращаемый при вызове ProcessURLAsync.Переменная firstFinishedTaskTask<TResult>, где TReturn - целое число.Задача уже завершена, но она ожидается для получения размера загруженного веб-сайта, как показано в следующем примере.

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

Необходимо запустить проект несколько раз проверить, загруженные длины не всегда отображаются в том же порядке.

Предупреждающее замечаниеВнимание

Можно использовать WhenAny в цикле, как описано в случае проблем, содержащих несколько задач.Однако другие способы эффективнее, если имеется большое количество задач в процесс.Дополнительные сведения и примеры см. в разделе Выполнение задачи, как они завершают:

Полный пример

В следующем коде полный текст файла MainWindow.xaml.vb или MainWindow.xaml.cs пример.Звездочка помечает элементы, добавленные для этого примера.

Обратите внимание, что необходимо добавить ссылку для System.Net.Http.

Можно загрузить проект из Пример Async: настройка приложения.

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

См. также

Ссылки

WhenAny

Основные понятия

Настройка асинхронного приложения (C# и Visual Basic)

Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic)

Другие ресурсы

Асинхронный пример: настройка приложения