在完成一个异步任务后取消剩余任务 (Visual Basic)
通过结合使用 Task.WhenAny 方法和 CancellationToken,可在一个任务完成时取消所有剩余任务。 WhenAny
方法采用任务集合中的一个参数。 该方法启动所有任务,并返回单个任务。 当集合中任意任务完成时,完成单个任务。
此示例演示如何结合使用取消标记与 WhenAny
保留任务集合中第一个要完成的任务,并取消剩余任务。 每个任务都下载网站内容。 本示例显示第一个完成的下载的内容长度,并取消其他下载。
备注
若要运行该示例,计算机上必须安装有 Visual Studio 2012 或更高版本和 .NET Framework 4.5 或更高版本。
下载示例
若要下载完整的 Windows Presentation Foundation (WPF) 项目,请参阅 Async Sample:Fine Tuning Your Application(异步示例:微调应用程序)。
解压缩下载的文件,然后启动 Visual Studio。
在菜单栏上,依次选择 “文件” 、 “打开” 和 “项目/解决方案” 。
在“打开项目”对话框中,打开保存已解压的示例代码的文件夹,然后打开 AsyncFineTuningVB 的解决方案 (.sln) 文件。
在“解决方案资源管理器”中,打开“CancelAfterOneTask”项目的快捷菜单,然后选择“设为启动项目”。
选择 F5 键运行该项目。
选择 Ctrl+F5 键运行该项目,而不进行调试。
运行程序若干次,以验证首先完成的下载是不同的。
如果不想下载项目,可在本主题末尾处查看 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
中,进行下列更改。 星号标记了代码文件中的更改。
注释禁止或删除循环。
创建一个查询,它在执行时将生成常规任务的集合。 每次调用
ProcessURLAsync
均在TResult
为整数时返回 Task<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)
通过调用
ToArray
来执行查询并启动任务。 下一步中应用WhenAny
方法将在不使用ToArray
的情况下执行查询并启动任务,但其他方法可能无法执行此操作。 最安全的做法是显式强制执行查询。' ***Use ToArray to execute the query and start the download tasks. Dim downloadTasks As Task(Of Integer)() = downloadTasksQuery.ToArray()
在任务集合上调用
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)
在此示例中,你只对首先完成的任务感兴趣。 因此,使用 CancellationTokenSource.Cancel 取消剩余任务。
' ***Cancel the rest of the downloads. You just want the first one. cts.Cancel()
最后,等待
finishedTask
检索下载内容的长度。Dim length = Await finishedTask resultsTextBox.Text &= vbCrLf & $"Length of the downloaded website: {length}" & vbCrLf
运行程序若干次,以验证首先完成的下载是不同的。
完整的示例
下列代码是示例的完整 MainWindow.xaml.vb or MainWindow.xaml.cs 文件。 对添加到此示例的元素进行了星号标记。
请注意,必须为 System.Net.Http 添加引用。
可以从 Async Sample:Fine Tuning Your Application(异步示例:微调应用程序)下载这些项目。
' 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.