當其中一項工作完成時,取消剩餘的非同步工作 (C# 和 Visual Basic)
藉由使用 Task.WhenAny 方法搭配 CancellationToken,您可以在一個工作完成之後,取消所有剩下的工作。 WhenAny 方法需要一個是工作集合的引數。 方法會啟動所有工作並傳回單一工作。 集合中的任何工作完成時,就完成單一工作。
這個範例展示如何使用消除標誌結合WhenAny 來保留第一個工作,讓其從工作集合中結束,並取消剩下的工作。 每個工作都會下載網站的內容。 這個範例顯示第一個下載內容完成的長度並移除其他下載。
注意事項 |
---|
若要執行範例,您必須將 Visual Studio 2012、Visual Studio 2013、Visual Studio Express 2012 for Windows Desktop、Visual Studio Express 2013 for Windows 或 .NET Framework 4.5 或 4.5.1 安裝在您的電腦上。 |
下載範例
您可以從非同步範例:微調應用程式下載完整的 Windows Presentation Foundation (WPF) 專案,然後依照下列步驟執行。
解壓縮您下載的檔案,然後啟動 Visual Studio。
在功能表列上,選擇 [檔案]、[開啟]、[專案/方案]。
在 [開啟專案] 對話方塊中,開啟含有已解壓縮之範例程式碼的資料夾,然後開啟 AsyncFineTuningCS 或 AsyncFineTuningVB 的方案 (.sln) 檔。
在 [方案總管] 中,開啟 [CancelAfterOneTask] 專案的捷徑功能表,然後選取 [設定為啟始專案]。
選擇 F5 鍵以執行專案。
選取 Ctrl+F5 鍵執行專案,但不偵錯。
執行程式幾次,驗證不同下載先完成。
如果您不要下載專案,您可以檢視本主題結尾的 MainWindow.xaml.vb 和 MainWindow.xaml.cs 檔案。
建置範例
本主題中的範例會加入至 取消一項非同步工作或工作清單 (C# 和 Visual Basic) 中開發的專案來消除工作清單。 這個範例使用相同的 UI,雖然 [取消 ] 按鈕不會明確使用。
若要建置範例,請依照<下載範例>中的指示逐步執行,不過選取 CancelAListOfTasks 為 [啟始專案]。 將本主題中的變更加入至該專案。
在 [CancelAListOfTasks] 專案的 MainWindow.xaml.vb 或 MainWindow.xaml.cs 檔案中,將每個網站的處理步驟從 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
// ***Bundle the processing steps for a website into one async method.
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
return urlContents.Length;
}
在 AccessTheWebAsync 中,本範例使用查詢、ToArray``1 方法或 WhenAny 方法來建立和啟動工作陣列。 WhenAny 應用到陣列上會傳回單一的工作,當等候時,可確保工作陣列中的第一個工作會完成。
在 AccessTheWebAsync 進行下列變更。 星號標示出程式碼檔案中的變更。
將迴圈標記為註解或刪除。
建立一個會在執行時產生泛型工作集合的查詢。 對 ProcessURLAsync 的每個呼叫都會傳回 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)
// ***Create a query that, when executed, returns a collection of tasks. IEnumerable<Task<int>> downloadTasksQuery = 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()
// ***Use ToArray to execute the query and start the download tasks. Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
在工作的集合上呼叫 WhenAny。 WhenAny 傳回 Task(Of Task(Of Integer)) 或 Task<Task<int>>。也就是說,WhenAny 會在等候時傳回相當於單一 Task(Of Integer) 或 Task<int> 的工作。 該單一工作是集合中要第一個完成的工作。 先完成的工作被指派給 firstFinishedTask。 firstFinishedTask 的類型是 Task,其中 TResult是一個整數,因為那是 ProcessURLAsync 的回傳類型。
' ***Call WhenAny and then await the result. The task that finishes ' first is assigned to firstFinishedTask. Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
// ***Call WhenAny and then await the result. The task that finishes // first is assigned to firstFinishedTask. Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
在此範例中,您只需要專注在先完成的工作上。 因此,請使用 CancellationTokenSource.Cancel 來取消剩餘工作。
' ***Cancel the rest of the downloads. You just want the first one. cts.Cancel()
// ***Cancel the rest of the downloads. You just want the first one. cts.Cancel();
最後,等候 firstFinishedTask 擷取下載內容的長度。
Dim length = Await firstFinishedTask resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website: {0}" & vbCrLf, length)
var length = await firstFinishedTask; resultsTextBox.Text += String.Format("\r\nLength of the downloaded website: {0}\r\n", length);
執行程式幾次,驗證不同下載先完成。
完整範例
下列程式碼是範例的完整 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 &=
'' String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, urlContents.Length)
''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 firstFinishedTask.
Dim firstFinishedTask 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 firstFinishedTask
resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website: {0}" & vbCrLf, length)
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/en-us/library/hh290138.aspx",
"https://msdn.microsoft.com/en-us/library/hh290140.aspx",
"https://msdn.microsoft.com/en-us/library/dd470362.aspx",
"https://msdn.microsoft.com/en-us/library/aa578028.aspx",
"https://msdn.microsoft.com/en-us/library/ms404677.aspx",
"https://msdn.microsoft.com/en-us/library/ff730837.aspx"
}
Return urls
End Function
End Class
' Sample output:
' Length of the downloaded website: 158856
' Download complete.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// Add a using directive and a reference for System.Net.Http.
using System.Net.Http;
// Add the following using directive.
using System.Threading;
namespace CancelAfterOneTask
{
public partial class MainWindow : Window
{
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
private async void startButton_Click(object sender, RoutedEventArgs e)
{
// Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
resultsTextBox.Clear();
try
{
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownload complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownload canceled.";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownload failed.";
}
// Set the CancellationTokenSource to null when the download is complete.
cts = null;
}
// You can still include a Cancel button if you want to.
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}
// Provide a parameter for the CancellationToken.
async Task AccessTheWebAsync(CancellationToken ct)
{
HttpClient client = new HttpClient();
// Call SetUpURLList to make a list of web addresses.
List<string> urlList = SetUpURLList();
// ***Comment out or delete the loop.
//foreach (var url in urlList)
//{
// // GetAsync returns a Task<HttpResponseMessage>.
// // Argument ct carries the message if the Cancel button is chosen.
// // ***Note that the Cancel button can cancel all remaining downloads.
// HttpResponseMessage response = await client.GetAsync(url, ct);
// // Retrieve the website contents from the HttpResponseMessage.
// byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
// resultsTextBox.Text +=
// String.Format("\r\nLength of the downloaded string: {0}.\r\n", urlContents.Length);
//}
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURLAsync(url, client, ct);
// ***Use ToArray to execute the query and start the download tasks.
Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
// ***Call WhenAny and then await the result. The task that finishes
// first is assigned to firstFinishedTask.
Task<int> firstFinishedTask = 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.
var length = await firstFinishedTask;
resultsTextBox.Text += String.Format("\r\nLength of the downloaded website: {0}\r\n", length);
}
// ***Bundle the processing steps for a website into one async method.
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
return urlContents.Length;
}
// Add a method that creates a list of web addresses.
private List<string> SetUpURLList()
{
List<string> urls = new List<string>
{
"https://msdn.microsoft.com",
"https://msdn.microsoft.com/en-us/library/hh290138.aspx",
"https://msdn.microsoft.com/en-us/library/hh290140.aspx",
"https://msdn.microsoft.com/en-us/library/dd470362.aspx",
"https://msdn.microsoft.com/en-us/library/aa578028.aspx",
"https://msdn.microsoft.com/en-us/library/ms404677.aspx",
"https://msdn.microsoft.com/en-us/library/ff730837.aspx"
};
return urls;
}
}
// Sample output:
// Length of the downloaded website: 158856
// Download complete.
}
請參閱
參考
概念
使用 Async 和 Await 設計非同步程式 (C# 和 Visual Basic)