完成一个异步任务后取消剩余异步任务 (Visual Basic)

通过将Task.WhenAny方法与CancellationToken结合使用,可以在一个任务完成时取消所有剩余任务。 该方法 WhenAny 采用一个参数,该参数是任务的集合。 该方法启动所有任务并返回单个任务。 当集合中的任何任务完成时,该单任务即告完成。

此示例演示如何结合使用取消标记与 WhenAny 保留任务集合中第一个要完成的任务,并取消剩余任务。 每个任务都会下载网站的内容。 本示例显示第一个完成的下载的内容长度,并取消其他下载。

注释

若要运行这些示例,必须在计算机上安装 Visual Studio 2012 或更高版本以及 .NET Framework 4.5 或更高版本。

下载示例

可以从异步示例下载完整的 Windows Presentation Foundation (WPF) 项目 :微调应用程序 ,然后执行以下步骤。

  1. 解压缩下载的文件,然后启动 Visual Studio。

  2. 在菜单栏上,选择 “文件”、“ 打开”、“ 项目/解决方案”。

  3. “打开项目 ”对话框中,打开保存解压缩的示例代码的文件夹,然后打开 AsyncFineTuningVB 的解决方案(.sln)文件。

  4. 解决方案资源管理器中,打开 CancelAfterOneTask 项目的快捷菜单,然后选择 “设置为启动项目”。

  5. 选择要运行项目的 F5 键。

    选择 Ctrl+F5 键以运行项目而不对其进行调试。

  6. 多次运行程序,验证不同的下载是否首先完成。

如果不想下载项目,可以在本主题末尾查看MainWindow.xaml.vb文件。

生成示例

本主题中的示例将扩展到在 “取消异步任务或任务列表” 中开发的项目,以取消任务列表。 该示例使用相同的 UI,尽管未显式使用 “取消” 按钮。

若要自行构建示例,请逐步按照“下载示例”部分中的说明进行操作,但选择 CancelAListOfTasks 作为 启动项目。 将本主题中的更改添加到该项目。

CancelAListOfTasks 项目的 MainWindow.xaml.vb 文件中,通过将每个网站的处理步骤从 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. 在任务集合上调用 WhenAnyWhenAny 返回 Task(Of Task(Of Integer))Task<Task<int>>。 也就是说,在等待时 WhenAny 将返回一个任务,它将评估单个的 Task(Of Integer)Task<int>。 该单个任务是集合中首先完成的任务。 首先完成的任务将分配给 finishedTaskfinishedTask 的类型是 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.

另请参阅