Condividi tramite


Annullare le attività asincrone rimanenti dopo che ne è stata completata una (C# e Visual Basic)

Tramite il metodo Task.WhenAny insieme a CancellationToken, è possibile annullare tutte le attività rimanenti quando un'attività è completa. Il metodo WhenAny accetta un argomento che rappresenta una raccolta di attività. Il metodo avvia tutte le attività e restituisce una singola attività. La singola attività è completa quando una qualsiasi attività nella raccolta è completa.

In questo esempio viene illustrato come utilizzare un token di annullamento con WhenAny per utilizzare la prima attività per completare la raccolta di attività e annullare le attività rimanenti. Ogni attività scarica il contenuto di un sito Web. L'esempio visualizza la lunghezza del contenuto del primo download per completare e annullare gli altri download.

Nota

Per eseguire gli esempi, è necessario che Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 per Windows Desktop,Visual Studio Express 2013 per Windows o .NET Framework 4.5 o 4.5.1 siano installati sul computer.

Download dell'esempio

È possibile scaricare il progetto completo Windows Presentation Foundation (WPF) da esempio di Async: ottimizzazione dell'applicazione e attenersi ai passaggi riportati di seguito.

  1. Decomprimere il file scaricato, quindi avviare Visual Studio.

  2. Sulla barra dei menu scegliere File, Apri, Progetto/Soluzione.

  3. Nella finestra di dialogo Apri progetto, aprire la cartella che contiene il codice di esempio che è stato decompresso quindi aprire il file di soluzione (.sln) per AsyncFineTuningCS o AsyncFineTuningVB.

  4. In Esplora soluzioni, aprire il menu di scelta rapida del progetto CancelAfterOneTask quindi scegliere Imposta come Progetto di Avvio.

  5. Premere il tasto F5 per eseguire il progetto.

    Scegliere i tasti CTRL+F5 per eseguire il progetto senza eseguirne il debug.

  6. Eseguire il programma più volte per verificare che vengano completati prima altri download.

Se non si desidera scaricare il progetto, è possibile recuperare i file di MainWindow.xaml.vb e MainWindow.xaml.cs alla fine di questo argomento.

Compilazione dell'esempio

L'esempio qui discusso si aggiunge al progetto sviluppato in Annullare un'attività asincrona o un elenco di attività (C# e Visual Basic) per annullare un elenco delle attività. L'esempio utilizza la stessa interfaccia utente, anche se il pulsante Annulla non viene utilizzato in modo esplicito.

Per compilare l'esempio manualmente, passo dopo passo, seguire le istruzioni nella sezione "download dell'esempio", ma scegliere CancelAListOfTasks come Progetto di avvio. Aggiungere le modifiche in questo argomento a tale progetto.

Nel file MainWindow.xaml.cs o MainWindow.xaml.vb del progetto CancelAListOfTasks avviare la transizione spostando i passaggi di elaborazione per ogni sito Web dal ciclo in AccessTheWebAsync nel metodo async riportato di seguito.

' ***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;
}

In AccessTheWebAsync, in questo esempio viene utilizzata una query, il metodo ToArray``1 e il metodo WhenAny per creare e avviare una matrice di attività. L'applicazione di WhenAny all' array restituisce una singola attività che, una volta messa in attesa, valuta quale sia la prima attività per raggiungere il completamento dell'array delle attività.

Effettuare le seguenti modifiche nel file AccessTheWebAsync. Gli asterischi contrassegnano le modifiche nel file di codice.

  1. Commentare o eliminare il ciclo.

  2. Creare una query che, quando eseguita, produce una raccolta di attività generiche. Ogni chiamata a ProcessURLAsync restituisce un Task dove TResult è un Integer.

    ' ***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. Chiamare ToArray per eseguire la query e avviare le attività. L'applicazione del metodo WhenAny al passaggio successivo esegue la query e inizia ad eseguire le attività senza utilizzare ToArray, ma altri metodi potrebbero non fare altrettanto. La pratica più sicura consiste nel forzare l'esecuzione della query in modo esplicito.

    ' ***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. Chiamare WhenAny sulla raccolta di attività. WhenAny restituisce un Task(Of Task(Of Integer)) o Task<Task<int>>. Ovvero, WhenAny restituisce un'attività che valuta un singolo Task(Of Integer) o a Task<int> quando è atteso. Quella singola attività è la prima attività nella raccolta da terminare. L'attività che è stata completata prima viene assegnata a firstFinishedTask. Il tipo firstFinishedTask è Task in cui TResult è un valore integer perché è il tipo restituito da 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. In questo esempio, si desidera controllare solo l'attività che termina per prima. Di conseguenza, utilizzare CancellationTokenSource.Cancel annullare le attività rimanenti.

    ' ***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. Infine, attendere firstFinishedTask per recuperare la lunghezza del contenuto scaricato.

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

Eseguire il programma più volte per verificare che vengano completati prima altri download.

Esempio completo

Il codice seguente è il testo completo del file MainWindow.xaml.vb o MainWindow.xaml.cs per l'esempio. Gli asterischi contrassegnano gli elementi aggiunti per questo esempio.

Notare che è necessario aggiungere un riferimento per System.Net.Http.

È possibile scaricare il progetto da Esempio Async: ottimizzazione dell'applicazione.

' 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.
}

Vedere anche

Riferimenti

WhenAny

Concetti

Ottimizzazione dell'applicazione Async (C# e Visual Basic)

Programmazione asincrona con Async e Await (C# e Visual Basic)

Altre risorse

Esempio asincrono: ottimizzazione dell'applicazione