Поделиться через


Отмена оставшихся асинхронных задач после завершения одного (Visual Basic)

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

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

Замечание

Для выполнения примеров необходимо установить Visual Studio 2012 или более поздней версии и .NET Framework 4.5 или более поздней версии на компьютере.

Скачивание примера

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

  1. Распакуйте скачанный файл и запустите Visual Studio.

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

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

  4. В обозревателе решений откройте контекстное меню проекта CancelAfterOneTask , а затем выберите "Задать в качестве проекта запуска".

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

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

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

Если вы не хотите скачать проект, просмотрите файл MainWindow.xaml.vb в конце этого раздела.

Создание примера

Пример в этом разделе добавляется в проект, разработанный в разделе "Отмена асинхронной задачи" или "Список задач ", чтобы отменить список задач. В примере используется тот же пользовательский интерфейс, хотя кнопка "Отмена " не используется явно.

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

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

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

В этом примере используются запрос, метод AccessTheWebAsync и метод ToArray для создания и запуска массива задач. Приложение массива 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)
    
  3. Позвоните по ToArray, чтобы выполнить запрос и начать задачи. Приложение WhenAny метода на следующем шаге будет выполнять запрос и запускать задачи без использования ToArray, но другие методы не могут. Самая безопасная практика — принудительное выполнение запроса явным образом.

    ' ***Use ToArray to execute the query and start the download tasks.
    Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
    
  4. Вызов WhenAny коллекции задач. WhenAny возвращает значение Task(Of Task(Of Integer)) или Task<Task<int>>. То есть возвращает задачу, WhenAny которая оценивается как одна Task(Of Integer) или Task<int> когда она ожидается. Эта одна задача — это первая задача в коллекции, которая будет завершена. Задача, завершиющаяся первым, назначается finishedTask. Тип finishedTask — это Task<TResult>, где TResult является целым числом, потому что это возвращаемый тип 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. В этом примере вас интересует только задача, которая завершается первой. Поэтому используйте CancellationTokenSource.Cancel для отмены оставшихся задач.

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

    Dim length = Await finishedTask
    resultsTextBox.Text &= vbCrLf & $"Length of the downloaded website:  {length}" & vbCrLf
    

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

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

Следующий код представляет собой полный файл 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 &=
        ''        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.

См. также