Condividi tramite


Flusso di controllo in programmi asincroni (C# e Visual Basic)

È possibile creare e gestire programmi asincroni più facilmente utilizzando le parole chiave Await e Async. Tuttavia, i risultati possono sorprendervi se non comprendete come il programma viene eseguito. Questo argomento traccia il flusso di controllo con un semplice programma asincrono per indicare quando il controllo passa da un metodo ad un altro e quali informazioni vengono trasferite ogni volta.

Nota

Le parole chiave Await e Async introdotte in Visual Studio 2012.

In generale, è bene contrassegnate i metodi contenenti codice asincrono con il modificatore async (C#) o Async (Visual Basic). In un metodo contrassegnato con un modificatore async, è possibile utilizzare un operatore Await (Visual Basic) o un operatore await C#) per specificare dove il metodo deve essere messo in pausa per aspettare il completamento del un processo asincrono chiamante. Per ulteriori informazioni, vedere Programmazione asincrona con Async e Await (C# e Visual Basic).

Nell'esempio vengono utilizzati i metodi async per scaricare il contenuto di un sito Web specificato come stringa e visualizzarne la lunghezza. Nell'esempio seguente sono contenuti due metodi.

  • startButton_Click, che chiama AccessTheWebAsync e visualizza il risultato.

  • AccessTheWebAsync, che scarica il contenuto di un sito Web come stringa e ne restituisce la lunghezza. AccessTheWebAsync utilizza un metodo asincrono HttpClient, GetStringAsync(String), per scaricare il contenuto.

Le linee di proiezione numerate vengono visualizzate in punti strategici del programma per aiutarvi a capire come il programma viene eseguito e cosa si verifica in ogni punto contrassegnato. Le linee di proiezione sono contrassegnate da "UNA" a "SEI". Le etichette rappresentano l'ordine in cui il programma raggiunge queste righe di codice.

Il codice seguente illustrata una struttura del programma.

Class MainWindow

    Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click

        ' ONE
        Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()

        ' FOUR
        Dim contentLength As Integer = Await getLengthTask

        ' SIX
        ResultsTextBox.Text &=
            String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)

    End Sub


    Async Function AccessTheWebAsync() As Task(Of Integer)

        ' TWO
        Dim client As HttpClient = New HttpClient() 
        Dim getStringTask As Task(Of String) = 
            client.GetStringAsync("https://msdn.microsoft.com")

        ' THREE
        Dim urlContents As String = Await getStringTask

        ' FIVE
        Return urlContents.Length
    End Function

End Class
public partial class MainWindow : Window
{
    // . . .
    private async void startButton_Click(object sender, RoutedEventArgs e)
    {
        // ONE
        Task<int> getLengthTask = AccessTheWebAsync();

        // FOUR
        int contentLength = await getLengthTask;

        // SIX
        resultsTextBox.Text +=
            String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
    }


    async Task<int> AccessTheWebAsync()
    {
        // TWO
        HttpClient client = new HttpClient();
        Task<string> getStringTask =
            client.GetStringAsync("https://msdn.microsoft.com");

        // THREE                 
        string urlContents = await getStringTask;

        // FIVE
        return urlContents.Length;
    }
}

Ogni percorso contrassegnato, da "UNO" a "SEI", visualizza le informazioni sullo stato corrente del programma. Il risultato è il seguente.

ONE:   Entering startButton_Click.
           Calling AccessTheWebAsync.

TWO:   Entering AccessTheWebAsync.
           Calling HttpClient.GetStringAsync.

THREE: Back in AccessTheWebAsync.
           Task getStringTask is started.
           About to await getStringTask & return a Task<int> to startButton_Click.

FOUR:  Back in startButton_Click.
           Task getLengthTask is started.
           About to await getLengthTask -- no caller to return to.

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

SIX:   Back in startButton_Click.
           Task getLengthTask is finished.
           Result from AccessTheWebAsync is stored in contentLength.
           About to display contentLength and exit.

Length of the downloaded string: 33946.

Impostare il Programma

È possibile scaricare il codice utilizzato in questo argomento da MSDN, oppure compilarlo manualmente.

Nota

Per eseguire l'esempio, è necessario che Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012, Visual Studio Express 2013 per Windows o .NET Framework 4.5 o 4.5.1 siano installati sul computer.

Scaricare il Programma

È possibile scaricare l'applicazione per questo argomento da Esempio Async: flusso di controllo nei programmi Async. I passaggi seguenti aprono ed eseguono il programma.

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

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

  3. Passare alla cartella che contiene il codice di esempio decompresso, aprire il file di soluzione (.sln) e quindi premere il tasto F5 per compilare ed eseguire il progetto.

Compilare il programma autonomamente

Il seguente progetto Windows Presentation Foundation (WPF) contiene l'esempio di codice per questo argomento.

Per eseguire il progetto, attenersi alla procedura descritta di seguito:

  1. Avviare Visual Studio.

  2. Nella barra dei menu, scegliere File, Nuovo, Progetto.

    Verrà visualizzata la finestra di dialogo Nuovo progetto.

  3. Nel riquadro Modelli installati, scegliere Visual Basic o Visual C# quindi scegliere Applicazione WPF dall'elenco di tipi di progetto.

  4. Immettere AsyncTracer come nome del progetto e quindi scegliere il pulsante OK.

    Il nuovo progetto verrà visualizzato in Esplora soluzioni.

  5. Nell'editor di codice di Visual Studio, scegliere la scheda MainWindow.xaml.

    Se la scheda non è visibile, scegliere dal menu di scelta rapida per MainWindow.xaml in Esplora soluzioni quindi scegliere Visualizza codice.

  6. Nel visualizzazione XAML di MainWindow.xaml, sostituire il codice con il seguente.

    <Window
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MainWindow"
        Title="Control Flow Trace" Height="350" Width="525">
        <Grid>
            <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="221,10,0,0" VerticalAlignment="Top" Width="75"/>
            <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="510" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" d:LayoutOverrides="HorizontalMargin"/>
    
        </Grid>
    </Window>
    
    <Window
            xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="AsyncTracer.MainWindow"
            Title="Control Flow Trace" Height="350" Width="592">
        <Grid>
            <Button x:Name="startButton" Content="Start&#xa;" HorizontalAlignment="Left" Margin="250,10,0,0" VerticalAlignment="Top" Width="75" Height="24"  Click="startButton_Click" d:LayoutOverrides="GridBox"/>
            <TextBox x:Name="resultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="576" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" Grid.ColumnSpan="3"/>
        </Grid>
    </Window>
    

    Una semplice finestra che contiene una casella di testo e un pulsante vengono visualizzati nella visualizzazione Progettazione di MainWindow.xaml.

  7. Aggiungere un riferimento per System.Net.Http.

  8. In Esplora soluzioni, aprire il menu di scelta rapida per MainWindow.xaml.vb o MainWindow.xaml.cs e scegliere Visualizza Codice.

  9. In MainWindow.xaml.vb o MainWindow.xaml.cs sostituire il codice con il seguente.

    ' Add an Imports statement and a reference for System.Net.Http. 
    Imports System.Net.Http
    
    Class MainWindow
    
        Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click
    
            ' The display lines in the example lead you through the control shifts.
            ResultsTextBox.Text &= "ONE:   Entering StartButton_Click." & vbCrLf &
                "           Calling AccessTheWebAsync." & vbCrLf
    
            Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
    
            ResultsTextBox.Text &= vbCrLf & "FOUR:  Back in StartButton_Click." & vbCrLf &
                "           Task getLengthTask is started." & vbCrLf &
                "           About to await getLengthTask -- no caller to return to." & vbCrLf
    
            Dim contentLength As Integer = Await getLengthTask
    
            ResultsTextBox.Text &= vbCrLf & "SIX:   Back in StartButton_Click." & vbCrLf &
                "           Task getLengthTask is finished." & vbCrLf &
                "           Result from AccessTheWebAsync is stored in contentLength." & vbCrLf &
                "           About to display contentLength and exit." & vbCrLf
    
            ResultsTextBox.Text &=
                String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)
        End Sub
    
    
        Async Function AccessTheWebAsync() As Task(Of Integer)
    
            ResultsTextBox.Text &= vbCrLf & "TWO:   Entering AccessTheWebAsync." 
    
            ' Declare an HttpClient object. 
            Dim client As HttpClient = New HttpClient()
    
            ResultsTextBox.Text &= vbCrLf & "           Calling HttpClient.GetStringAsync." & vbCrLf
    
            ' GetStringAsync returns a Task(Of String).  
            Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
    
            ResultsTextBox.Text &= vbCrLf & "THREE: Back in AccessTheWebAsync." & vbCrLf &
                "           Task getStringTask is started." 
    
            ' AccessTheWebAsync can continue to work until getStringTask is awaited.
    
            ResultsTextBox.Text &=
                vbCrLf & "           About to await getStringTask & return a Task(Of Integer) to StartButton_Click." & vbCrLf
    
            ' Retrieve the website contents when task is complete. 
            Dim urlContents As String = Await getStringTask
    
            ResultsTextBox.Text &= vbCrLf & "FIVE:  Back in AccessTheWebAsync." &
                vbCrLf & "           Task getStringTask is complete." &
                vbCrLf & "           Processing the return statement." &
                vbCrLf & "           Exiting from AccessTheWebAsync." & vbCrLf
    
            Return urlContents.Length
        End Function 
    
    End Class
    
    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;
    
    namespace AsyncTracer
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private async void startButton_Click(object sender, RoutedEventArgs e)
            {
                // The display lines in the example lead you through the control shifts.
                resultsTextBox.Text += "ONE:   Entering startButton_Click.\r\n" +
                    "           Calling AccessTheWebAsync.\r\n";
    
                Task<int> getLengthTask = AccessTheWebAsync();
    
                resultsTextBox.Text += "\r\nFOUR:  Back in startButton_Click.\r\n" +
                    "           Task getLengthTask is started.\r\n" +
                    "           About to await getLengthTask -- no caller to return to.\r\n";
    
                int contentLength = await getLengthTask;
    
                resultsTextBox.Text += "\r\nSIX:   Back in startButton_Click.\r\n" +
                    "           Task getLengthTask is finished.\r\n" +
                    "           Result from AccessTheWebAsync is stored in contentLength.\r\n" +
                    "           About to display contentLength and exit.\r\n";
    
                resultsTextBox.Text +=
                    String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
            }
    
    
            async Task<int> AccessTheWebAsync()
            {
                resultsTextBox.Text += "\r\nTWO:   Entering AccessTheWebAsync.";
    
                // Declare an HttpClient object.
                HttpClient client = new HttpClient();
    
                resultsTextBox.Text += "\r\n           Calling HttpClient.GetStringAsync.\r\n";
    
                // GetStringAsync returns a Task<string>. 
                Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
    
                resultsTextBox.Text += "\r\nTHREE: Back in AccessTheWebAsync.\r\n" +
                    "           Task getStringTask is started.";
    
                // AccessTheWebAsync can continue to work until getStringTask is awaited.
    
                resultsTextBox.Text +=
                    "\r\n           About to await getStringTask and return a Task<int> to startButton_Click.\r\n";
    
                // Retrieve the website contents when task is complete. 
                string urlContents = await getStringTask;
    
                resultsTextBox.Text += "\r\nFIVE:  Back in AccessTheWebAsync." +
                    "\r\n           Task getStringTask is complete." +
                    "\r\n           Processing the return statement." +
                    "\r\n           Exiting from AccessTheWebAsync.\r\n";
    
                return urlContents.Length;
            }
        }
    }
    
  10. Premere il tasto F5 per eseguire il programma, quindi scegliere il pulsante Avvia.

    Dovrebbe comparire il seguente output.

    ONE:   Entering startButton_Click.
               Calling AccessTheWebAsync.
    
    TWO:   Entering AccessTheWebAsync.
               Calling HttpClient.GetStringAsync.
    
    THREE: Back in AccessTheWebAsync.
               Task getStringTask is started.
               About to await getStringTask & return a Task<int> to startButton_Click.
    
    FOUR:  Back in startButton_Click.
               Task getLengthTask is started.
               About to await getLengthTask -- no caller to return to.
    
    FIVE:  Back in AccessTheWebAsync.
               Task getStringTask is complete.
               Processing the return statement.
               Exiting from AccessTheWebAsync.
    
    SIX:   Back in startButton_Click.
               Task getLengthTask is finished.
               Result from AccessTheWebAsync is stored in contentLength.
               About to display contentLength and exit.
    
    Length of the downloaded string: 33946.
    

Traccia il Programma

Passaggi UNO e DUE

Le prime due righe di proiezione tracciano il percorso come startButton_Click chiama AccessTheWebAsynce AccessTheWebAsync chiama il metodo asincrono HttpClient GetStringAsync(String). Nell'immagine seguente vengono descritte le chiamate dal metodo.

Passaggi UNO e DUE

Il tipo restituito di entrambi gli AccessTheWebAsync e client.GetStringAsync è Task. Per AccessTheWebAsync, TResult è un Integer. Per GetStringAsync, TResult è una stringa. Per ulteriori informazioni sui tipi restituiti dai metodi async, vedere Tipi restituiti asincroni (C# e Visual Basic).

Un'attività di ritorno di un metodo async restituisce un'istanza quando il controllo torna al chiamante. Il controllo ritorna generalmente da un metodo async al suo chiamante quando un operatore await o Await sin incontra nel nel metodo chiamato o quando il metodo chiamato termina. Le linee di proiezione contrassegnate da "TRE" a "SEI" tracciano questa parte del processo.

Passaggio TRE

In AccessTheWebAsync, il metodo asincrono GetStringAsync(String) viene chiamato per scaricare il contenuto della pagina Web di destinazione. Il controllo restituisce da client.GetStringAsync a AccessTheWebAsync quando client.GetStringAsync viene restituito.

Il metodo client.GetStringAsync restituisce un'attività di stringa assegnata alla variabile getStringTask in AccessTheWebAsync. La riga seguente nel programma di esempio illustra la chiamata a client.GetStringAsync e l'assegnazione.

Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");

È possibile pensare all'attività come promessa per client.GetStringAsync in modo da produrre, eventualmente, una stringa effettiva. Contemporaneamente, se AccessTheWebAsync ha del lavoro da eseguire che non dipende dalla stringa promessa da client.GetStringAsync, tale lavoro può continuare finché client.GetStringAsync rimane in attesa. Nell'esempio, le seguenti righe di output, contrassegnate con "TRE," rappresentano la possibilità di eseguire il lavoro in modo indipendente

THREE: Back in AccessTheWebAsync.
           Task getStringTask is started.
           About to await getStringTask & return a Task<int> to startButton_Click.

La seguente istruzione sospende lo stato di avanzamento in AccessTheWebAsync quando getStringTask è atteso.

Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;

Nell'immagine seguente è illustrato il flusso di controllo da client.GetStringAsync all'assegnazione a getStringTask e dalla creazione getStringTask all'applicazione di un operatore di attesa.

Passaggio TRE

L'await expression sospende AccessTheWebAsync fino al completamento di client.GetStringAsync. Contemporaneamente, restituisce il controllo al chiamante di AccessTheWebAsync, startButton_Click.

Nota

In genere, si attende la chiamata verso un metodo asincrono immediatamente.Ad esempio, una delle seguenti assegnazioni potrebbe sostituire il codice precedente e attende quindi getStringTask:

  • Visual Basic: Dim urlContents As String = Await client.GetStringAsync("https://msdn.microsoft.com")

  • C#: string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");

In questo argomento, l'operatore di attesa viene applicato successivamente per inserire delle righe di output che marcano il flusso di controllo attraverso il programma.

Passaggio QUATTRO

Il tipo restituito dichiarato di AccessTheWebAsync è Task(Of Integer) in Visual Basic e in Task<int> in C#. Pertanto, quando AccessTheWebAsync viene sospeso, restituisce un'attività Integer a startButton_Click. È necessario comprendere che l'attività restituita non è getStringTask. L'attività restituita è una nuova attività dell'integer che rappresenta cosa rimane da eseguire nel metodo sospeso, AccessTheWebAsync. L'attività è una promessa di AccessTheWebAsync di produrre un Integer quando l'attività è completa.

La seguente istruzione assegna questa attività alla variabile getLengthTask.

Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();

Come in AccessTheWebAsync, startButton_Click può continuare con il lavoro che non dipende dai risultati dell'attività asincrona (getLengthTask) finché l'attività rimane in attesa. Le seguenti righe di output rappresentano quel lavoro.

FOUR:  Back in startButton_Click.
           Task getLengthTask is started.
           About to await getLengthTask -- no caller to return to.

Lo stato di avanzamento in startButton_Click viene sospeso quando getLengthTask è in attesa. La seguente istruzione di assegnazione sospende startButton_Click finché AccessTheWebAsync non viene completato.

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

Nella figura seguente, le frecce mostrano il flusso di controllo dall'espressione di attesa in AccessTheWebAsync all'assegnazione di un valore a getLengthTask, seguita da una normale procedura in startButton_Click finché getLengthTask rimane in attesa.

Passaggio QUATTRO

Passaggio CINQUE

Quando client.GetStringAsync segnala che è completo, l'elaborazione in AccessTheWebAsync viene rilasciata dalla sospensione e può procedere oltre l'istruzione di attesa. Le seguenti righe di output rappresentano la ripresa di elaborazione.

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

L'operando dell'istruzione return, urlContents.Length, è archiviato nell'attività che AccessTheWebAsync restituisce. L'espressione di attesa recupera il valore da getLengthTask nel startButton_Click.

L'immagine seguente riporta il trasferimento di controllo dopo che client.GetStringAsync (e getStringTask) viene completato.

Passaggio CINQUE

AccessTheWebAsync viene eseguita fino al completamento, e restituisce il controllo a startButton_Click, in attesa del completamento.

Passaggio SEI

Quando AccessTheWebAsync segnala di essere completo, l'esecuzione può continuare oltre l'istruzione di attesa in startButton_Async. Infatti, il programma non ha più altro da fare.

Le seguenti righe di output rappresentano la ripresa di elaborazione in startButton_Async:

SIX:   Back in startButton_Click.
           Task getLengthTask is finished.
           Result from AccessTheWebAsync is stored in contentLength.
           About to display contentLength and exit.

L'espressione di attesa recupera da getLengthTask il valore integer che corrisponde all'operando dell'istruzione return contenuta in AccessTheWebAsync. La seguente istruzione assegna quel valore alla variabile contentLength.

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

Nella figura seguente viene riportata la restituzione del controllo da AccessTheWebAsync a startButton_Click.

Passaggio SEI

Vedere anche

Attività

Procedura dettagliata: accesso al Web tramite Async e Await (C# e Visual Basic)

Procedura dettagliata: utilizzo del debugger con metodi Async

Concetti

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

Tipi restituiti asincroni (C# e Visual Basic)

Altre risorse

Esempio asincrono: Flusso di controllo nei programmi asincroni (C# e Visual Basic)