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

通过使用 CancellationTokenTask.WhenAny 方法,可在一项任务完成时,取消所有剩下的任务。 WhenAny 方法采用任务集合参数。 该方法启动所有任务并返回单个任务。 当集合中的任意任务完成时,该单个任务已完成。

此示例演示如何将取消标记与 WhenAny 结合使用以保持第一个任务从任务集合中完成并取消剩余任务。 每个任务下载网站的内容。 该示例显示要完成的首次下载的内容的长度并取消其他下载。

备注

若要运行此示例,需在计算机上安装 Visual Studio 2012、Visual Studio 2013、Visual Studio Express 2012 for Windows Desktop(Visual Studio Express 2012 for Windows Desktop)、Visual Studio Express 2013 for Windows 或 .NET Framework 4.5 或 4.5.1。

下载示例

您可以从 Async Sample: Fine Tuning Your Application(异步示例:微调应用程序)下载完整的 Windows Presentation Foundation (WPF) 项目,然后按照这些步骤操作。

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

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

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

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

  5. 选择 F5 键运行项目。

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

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

如果您不希望下载该项目,可以在本主题结尾查看 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 中进行以下更改。 星号标记出代码文件中的更改。

  1. 注释掉或删除循环。

  2. 创建在执行时生成泛型任务集合的查询。 每个 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);
    
  3. 调用 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();
    
  4. 调用任务的集合上的 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);
    
  5. 在此示例中,您仅对首先完成的任务感兴趣。 因此,请使用 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();
    
  6. 最后,等待 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.
}

请参见

参考

WhenAny

概念

微调异步应用程序(C# 和 Visual Basic)

使用 Async 和 Await 的异步编程(C# 和 Visual Basic)

其他资源

Async Sample: Fine Tuning Your Application