Отменить оставшиеся задачи после завершения одной из них (C# и Visual Basic)

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

В этом примере показано, как использовать токен отмены совместно с 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. В Обозревателе решений откройте контекстное меню для проекта CancelAfterOneTask, а затем выберите Назначить автозапускаемым проектом.

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

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

  6. Запустите программу несколько раз проверить различные загрузки; сначала.

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

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

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

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

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

' ***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
// ***Bundle the processing steps for a website into one async method.
async Task<int> ProcessURLAsync(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;
}

В AccessTheWebAsync, в этом примере используется запрос, метод ToArray<TSource> и метод WhenAny используется для создания и запуска задач массив.Приложение WhenAny в массив возвращает одну задачу, которая после ожидания оценивает первую задачу, которая должна выполниться в массиве задач.

Внесите следующие изменения в AccessTheWebAsync.Звездочками помечаются изменения в файле кода.

  1. Закомментируйте или удалите цикл.

  2. Создайте запрос, который после выполнения возвращает коллекцию универсальных задач.Каждый вызов ProcessURLAsync возвращает Task<TResult>, где TResult - целое число.

    ' ***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)
    
    // ***Create a query that, when executed, returns a collection of tasks.
    IEnumerable<Task<int>> downloadTasksQuery =
        from url in urlList select ProcessURLAsync(url, client, ct);
    
  3. Вызовите ToArray для выполнения запроса и запуска задач.Приложение метода WhenAny в следующем шаге выполнит запрос и запустит задачи без использования ToArray, но другие методы могут и не сделать этого.Наиболее безопасным подходом будет принудительное явное выполнение запроса.

    ' ***Use ToArray to execute the query and start the download tasks. 
    Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
    
    // ***Use ToArray to execute the query and start the download tasks. 
    Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
    
  4. Вызовите WhenAny в коллекцию задач.WhenAny возвращает Task(Of Task(Of Integer)) или Task<Task<int>>.Иными словами, WhenAny возвращает задачу, которая равна одной Task(Of Integer) или Task<int> после ожидания.Та одна задача является задачей, которая первая завершится в коллекции.Для первой завершившейся присвоена значение firstFinishedTask.Тип firstFinishedTask является Task<TResult>, где TResult — целое число, поскольку это возвращаемый тип ProcessURLAsync.

    ' ***Call WhenAny and then await the result. The task that finishes 
    ' first is assigned to firstFinishedTask.
    Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
    
    // ***Call WhenAny and then await the result. The task that finishes 
    // first is assigned to firstFinishedTask.
    Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
    
  5. В этом примере представляет интерес только та задача, которая завершится первой.Поэтому используйте CancellationTokenSource.Cancel для отмены остальных задач.

    ' ***Cancel the rest of the downloads. You just want the first one.
    cts.Cancel()
    
    // ***Cancel the rest of the downloads. You just want the first one.
    cts.Cancel();
    
  6. Наконец, подождите firstFinishedTask для получения размера загруженного содержимого.

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

Запустите программу несколько раз проверить различные загрузки; сначала.

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

В следующем коде полный файл 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 & "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 &=
        ''        String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, urlContents.Length)
        ''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 firstFinishedTask.
        Dim firstFinishedTask 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 firstFinishedTask
        resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website:  {0}" & vbCrLf, length)
    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 downloaded website:  158856

' Download 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 CancelAfterOneTask
{
    public partial class MainWindow : Window
    {
        // Declare a System.Threading.CancellationTokenSource.
        CancellationTokenSource cts;

        public MainWindow()
        {
            InitializeComponent();
        }


        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            // Instantiate the CancellationTokenSource.
            cts = new CancellationTokenSource();

            resultsTextBox.Clear();

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

            // Set the CancellationTokenSource to null when the download is complete.
            cts = null;
        }


        // You can still include a Cancel button if you want to.
        private void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            if (cts != null)
            {
                cts.Cancel();
            }
        }


        // Provide a parameter for the CancellationToken.
        async Task AccessTheWebAsync(CancellationToken ct)
        {
            HttpClient client = new HttpClient();

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

            // ***Comment out or delete the loop.
            //foreach (var url in urlList)
            //{
            //    // GetAsync returns a Task<HttpResponseMessage>. 
            //    // Argument ct carries the message if the Cancel button is chosen. 
            //    // ***Note that the Cancel button can cancel all remaining downloads.
            //    HttpResponseMessage response = await client.GetAsync(url, ct);

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

            //    resultsTextBox.Text +=
            //        String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length);
            //}

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

            // ***Use ToArray to execute the query and start the download tasks. 
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();

            // ***Call WhenAny and then await the result. The task that finishes 
            // first is assigned to firstFinishedTask.
            Task<int> firstFinishedTask = 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.
            var length = await firstFinishedTask;
            resultsTextBox.Text += String.Format("\r\nLength of the downloaded website:  {0}\r\n", length);
        }


        // ***Bundle the processing steps for a website into one async method.
        async Task<int> ProcessURLAsync(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;
        }


        // Add a method that creates a list of web addresses.
        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "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;
        }
    }
    // Sample output:

    // Length of the downloaded website:  158856

    // Download complete.
}

См. также

Ссылки

WhenAny

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

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

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

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

Пример Async: настройка приложения