启动多个异步任务并在其完成时进行处理(C# 和 Visual Basic)

通过使用 Task.WhenAny,可以同时启动多个任务并在任务完成时依次对其进行处理,而不是按照任务启动的顺序进行处理。

下面的示例使用查询创建任务集合。 每个任务下载指定网站的内容。 在 while 循环的每次迭代中,对 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。

下载示例

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

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

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

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

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

  5. 选择 F5 键运行项目。

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

  6. 多次运行程序以验证下载长度并不以相同顺序显示。

如果您不希望下载该项目,可以在本主题结尾查看 MainWindow.xaml.vb 和 MainWindow.xaml.cs 文件。

生成示例

此示例添加到 在完成一个异步任务后取消剩余任务(C# 和 Visual Basic) 中开发的代码并使用相同的 UI。

要自己生成示例,请按照“下载示例”部分的说明一步一步操作,但请选择“CancelAfterOneTask”作为“启动项目”。 添加本主题中的更改到该项目中的 AccessTheWebAsync 方法。 这些更改标记有星号。

“CancelAfterOneTask”项目已包括当执行时会创建任务集合的查询。 以下代码中每个 ProcessURLAsync 的调用返回 Task(如果 TResult 为整数)。

Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
    From url In urlList Select ProcessURLAsync(url, client, ct)
IEnumerable<Task<int>> downloadTasksQuery =
    from url in urlList select ProcessURL(url, client, ct);

在项目的 MainWindow.xaml.vb 或 MainWindow.xaml.cs 文件中,对 AccessTheWebAsync 方法进行以下更改。

  • 通过应用 Enumerable.ToList``1 而非 ToArray``1 执行查询。

    Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()
    
    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
    
  • 添加为集合中每个任务执行以下步骤的 while 循环。

    1. 等待 WhenAny 的调用,识别集合中的第一个任务完成其下载。

      Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
      
      Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
      
    2. 从集合中移除该任务。

      downloadTasks.Remove(firstFinishedTask)
      
      downloadTasks.Remove(firstFinishedTask);
      
    3. 等待 firstFinishedTask,其由 ProcessURLAsync 的调用返回。 firstFinishedTask 变量是 Task,其中 TReturn 是整数。 任务已完成,但你要等待以检索该下载网站的长度,如下例所示。

      Dim length = Await firstFinishedTask
      resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website:  {0}" & vbCrLf, length)
      
      int length = await firstFinishedTask;
      resultsTextBox.Text += String.Format("\r\nLength of the download:  {0}", length);
      

你应运行项目多次以验证下载的长度并不总是以同一顺序出现。

警告

可以使用循环中的 WhenAny以解决涉及少量任务的问题,如示例所示。但是,如果您有大量任务需要处理,则其他方法更为有效。有关更多信息和示例,请参见Processing Tasks as they complete(完成时处理任务)。

完整的示例

下面的代码是该示例的 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 & "Downloads complete." 

        Catch ex As OperationCanceledException
            resultsTextBox.Text &= vbCrLf & "Downloads canceled." & vbCrLf

        Catch ex As Exception
            resultsTextBox.Text &= vbCrLf & "Downloads 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()

        ' ***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 ToList to execute the query and start the download tasks.  
        Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()

        ' ***Add a loop to process the tasks one at a time until none remain. 
        While downloadTasks.Count > 0
            ' ***Identify the first task that completes. 
            Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)

            ' ***Remove the selected task from the list so that you don't 
            ' process it more than once.
            downloadTasks.Remove(firstFinishedTask)

            ' ***Await the first completed task and display the results. 
            Dim length = Await firstFinishedTask
            resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website:  {0}" & vbCrLf, length)
        End While 

    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 download:  226093 
' Length of the download:  412588 
' Length of the download:  175490 
' Length of the download:  204890 
' Length of the download:  158855 
' Length of the download:  145790 
' Length of the download:  44908 
' Downloads 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 ProcessTasksAsTheyFinish
{
    public partial class MainWindow : Window
    {
        // Declare a System.Threading.CancellationTokenSource.
        CancellationTokenSource cts;

        public MainWindow()
        {
            InitializeComponent();
        }

        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            resultsTextBox.Clear();

            // Instantiate the CancellationTokenSource.
            cts = new CancellationTokenSource();

            try
            {
                await AccessTheWebAsync(cts.Token);
                resultsTextBox.Text += "\r\nDownloads complete.";
            }
            catch (OperationCanceledException)
            {
                resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
            }
            catch (Exception)
            {
                resultsTextBox.Text += "\r\nDownloads failed.\r\n";
            }

            cts = null;
        }


        private void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            if (cts != null)
            {
                cts.Cancel();
            }
        }


        async Task AccessTheWebAsync(CancellationToken ct)
        {
            HttpClient client = new HttpClient();

            // Make a list of web addresses.
            List<string> urlList = SetUpURLList();

            // ***Create a query that, when executed, returns a collection of tasks.
            IEnumerable<Task<int>> downloadTasksQuery =
                from url in urlList select ProcessURL(url, client, ct);

            // ***Use ToList to execute the query and start the tasks. 
            List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

            // ***Add a loop to process the tasks one at a time until none remain. 
            while (downloadTasks.Count > 0)
            {
                    // Identify the first task that completes.
                    Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);

                    // ***Remove the selected task from the list so that you don't 
                    // process it more than once.
                    downloadTasks.Remove(firstFinishedTask);

                    // Await the completed task. 
                    int length = await firstFinishedTask;
                    resultsTextBox.Text += String.Format("\r\nLength of the download:  {0}", length);
            }
        }


        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290136.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;
        }


        async Task<int> ProcessURL(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;
        }
    }
}

// Sample Output: 

// Length of the download:  226093 
// Length of the download:  412588 
// Length of the download:  175490 
// Length of the download:  204890 
// Length of the download:  158855 
// Length of the download:  145790 
// Length of the download:  44908 
// Downloads complete.

请参见

参考

WhenAny``1

概念

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

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

其他资源

Async 示例:微调应用程序