藉由使用 Task.WhenAny 方法搭配 CancellationToken,當一個任務完成時,您可以取消所有剩餘的任務。
WhenAny 方法接收一個參數,它是任務集合。 方法會啟動所有工作,並傳回單一工作。 當集合中的任何工作完成時,單一工作就會完成。
此範例說明如何將取消標記與WhenAny結合使用,以保留第一個完成的工作,並取消剩餘的工作集。 每項工作都會下載網站的內容。 此範例會顯示第一次下載的內容長度,以完成並取消其他下載。
備註
若要執行範例,您必須在計算機上安裝Visual Studio 2012或更新版本和 .NET Framework 4.5 或更新版本。
下載範例
您可以從 異步範例:微調您的應用程式 ,然後遵循下列步驟,下載完整的 Windows Presentation Foundation (WPF) 專案。
解壓縮您下載的檔案,然後啟動 Visual Studio。
在功能表欄上,選擇 [ 檔案]、[ 開啟]、[ 專案/方案]。
在 [ 開啟專案 ] 對話框中,開啟保存解壓縮範例程式代碼的資料夾,然後開啟 AsyncFineTuningVB 的解決方案 (.sln) 檔案。
在 [方案總管] 中,開啟 CancelAfterOneTask 專案的快捷方式功能表,然後選擇 [ 設定為啟始專案]。
選擇 F5 鍵以執行專案。
選擇 Ctrl+F5 鍵來執行專案,而不進行偵錯。
執行程式數次,以確認不同的下載會先完成。
如果您不想下載專案,您可以在本主題結尾檢閱MainWindow.xaml.vb檔案。
建構範例
本主題中的範例會將開發於取消異步任務或任務清單的專案進行擴充,以取消任務清單。 雖然未明確使用 [取消 ] 按鈕,但此範例會使用相同的UI。
若要自行建置範例,請逐步遵循「下載範例」一節中的指示,但選擇 CancelAListOfTasks 作為 StartUp 專案。 將本主題中的變更新增至該專案。
在 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都會回傳 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)呼叫
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或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.