Rozpoczynanie wielu zadań asynchronicznych i przetwarzanie ich w chwili zakończenia (C# i Visual Basic)

Za pomocą Task.WhenAny, można uruchamiać wiele zadań w tym samym czasie i przetwarzać je jedno po zakończeniu innego, zamiast przetwarzać je w kolejności, w której są uruchamiane.

Poniższy przykład używa zapytania do utworzenia kolekcji zadań.Każde zadanie powoduje pobieranie zawartości określonej witryny sieci Web.W każdej iteracji pętli while oczekiwane wywołanie do funkcji WhenAny zwraca zadanie w zbiorze zadań, którego pobieranie najpierw się zakończy.To zadanie jest usuwane z kolekcji i przetwarzane.Pętla powtarza się, dopóki kolekcja nie będzie już zawierała zadań.

[!UWAGA]

Aby uruchomić przykłady, na komputerze musisz mieć zainstalowane programy Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 for Windows Desktop, Visual Studio Express 2013 for Windows lub .NET Framework w wersji 4.5 lub 4.5.1.

Pobieranie przykładu

Można pobrać pełny projekt Windows Presentation Foundation (WPF) z Próbka asynchroniczna: Dostrajanie aplikacji, a następnie należy wykonać poniższe kroki.

  1. Przeprowadź dekompresję pobranego pliku, a następnie uruchom Visual Studio.

  2. Na pasku menu wybierz kolejno opcje Plik, Otwórz i Projekt/rozwiązanie.

  3. W oknie dialogowym Otwórz projekt, otwórz folder, który zawiera przykładowy kod, który został zdekompresowany, a następnie otwórz plik rozwiązania (.sln) dla AsyncFineTuningCS lub AsyncFineTuningVB.

  4. W Eksploratorze rozwiązań otwórz menu skrótów dla projektu ProcessTasksAsTheyFinish, a następnie wybierz polecenie Ustaw jako projekt startowy.

  5. Wybierz klawisz F5, aby uruchomić aplikację.

    Wybierz klawisze Ctrl + F5, aby uruchomić projekt bez debugowania go.

  6. Uruchom projekt kilka razy, aby sprawdzić, czy pobrane długości nie pojawiają się zawsze w tej samej kolejności.

Jeśli nie chcesz pobrać projektu, możesz przejrzeć pliki MainWindow.xaml.vb i MainWindow.xaml.cs na końcu tego tematu.

Budowanie przykładu

Ten przykład jest dodatkiem do kodu utworzonego w artykule Anulowanie pozostałych zadań asynchronicznych po zakończeniu jednego (C# i Visual Basic) i używa tego samego interfejsu użytkownika.

Aby zbudować przykład samodzielnie, krok po kroku, postępuj zgodnie z instrukcjami podanymi w części Pobieranie przykładu, ale wybierając CancelAfterOneTask jako Projekt startowy.Dodaj zmiany w tym temacie do metody AccessTheWebAsync w tym projekcie.Zmiany są oznaczone gwiazdkami.

Projekt CancelAfterOneTask zawiera już zapytanie, którego wykonanie powoduje utworzenie kolekcji zadań.Każde wywołanie metody ProcessURLAsync w poniższym kodzie zwróci element Task, gdzie TResult jest liczbą całkowitą.

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);

W pliku MainWindow.xaml.vb lub MainWindow.xaml.cs w projekcie dokonaj następujących zmian AccessTheWebAsync metody.

  • Wykonanie zapytania przez zastosowanie kodu Enumerable.ToList``1 zamiast ToArray``1.

    Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()
    
    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
    
  • Dodaj pętlę tymczasową, która wykonuje następujące kroki dla każdego zadania w kolekcji.

    1. Czeka, aby wywołanie WhenAny zidentyfikowało pierwsze zadanie w kolekcji, aby zakończyć jego pobranie.

      Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
      
      Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
      
    2. Usuwa to zadanie z kolekcji.

      downloadTasks.Remove(firstFinishedTask)
      
      downloadTasks.Remove(firstFinishedTask);
      
    3. Czeka na firstFinishedTask, zwracany przez wywołanie ProcessURLAsync.Zmienna firstFinishedTask ma wartość Task, gdzie TReturn jest liczbą całkowitą.Zadanie zostało już wykonane, ale czeka na pobranie długości pobranej witryny sieci web, jak w poniższym przykładzie.

      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);
      

Należy uruchomić projekt kilka razy, aby sprawdzić, czy pobrane długości nie pojawiają się zawsze w tej samej kolejności.

Informacje dotyczące przestrogiPrzestroga

Można użyć WhenAny w pętli, jak opisano w przykładzie, aby rozwiązać problemy związane z małą liczbą zadań.Jednak inne podejścia są bardziej efektywne, jeśli masz dużą liczbę zadań do przetworzenia.Aby uzyskać dodatkowe informacje i obejrzeć przykłady, zobacz Przetwarzanie zadań w miarę ich ukańczania.

Kompletny przykład

Poniższy kod jest pełnym tekstem pliku MainWindow.xaml.vb lub MainWindow.xaml.cs dla przykładu.Gwiazdki oznaczają elementy, które zostały dodane dla tego przykładu.

Należy zauważyć, że należy dodać odwołanie do System.Net.Http.

Można ściągnąć projekt z 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 & "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.

Zobacz też

Informacje

WhenAny``1

Koncepcje

Dostrajanie aplikacji Async (C# i Visual Basic)

Programowanie asynchroniczne z Async i Await (C# i Visual Basic)

Inne zasoby

Próbka asynchroniczna: Dostrajanie aplikacji