Cancel Remaining Async Tasks after One Is Complete (Visual Basic) (Отмена оставшихся асинхронных задач после завершения одной из них в Visual Basic)

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

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

Примечание.

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

Загрузка примера

Вы можете скачать весь проект Windows Presentation Foundation (WPF) со страницы Пример асинхронности. Тонкая настройка приложения, а затем выполнить необходимые действия.

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

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

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

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

  5. Нажмите клавишу F5, чтобы запустить проект.

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

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

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

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

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

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

В файле MainWindow.xaml.vb проекта 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

В AccessTheWebAsyncэтом примере используется запрос, ToArray метод и метод для создания и 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)
    
  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.

Можно загрузить проект со страницы Пример асинхронности. Тонкая настройка приложения.

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

См. также