Flux de contrôle dans les programmes Async (C# et Visual Basic)

Vous pouvez écrire et gérer des programmes asynchrones plus facilement à l'aide des mots clés Async et Await. Toutefois, les résultats peuvent vous étonner si vous ne comprenez pas comment votre programme s'exécute. Cette rubrique suit l'ordre d'exécution via un programme asynchrone simple pour vous indiquer lorsque le contrôle se déplace d'une méthode à une autre et quelles informations sont transférées à chaque fois.

Notes

Les mots clés Async et Await ont été introduits dans Visual Studio 2012.

En général, vous marquez les méthodes qui contiennent le code asynchrone avec le modificateur Async (Visual Basic) ou async (C#). Dans une méthode qui est marquée avec un modificateur async, vous pouvez utiliser un opérateur Await (Visual Basic) ou await (C#) pour spécifier où la méthode se suspend pour attendre qu'un processus asynchrone appelé se termine. Pour plus d'informations, consultez Programmation asynchrone avec Async et Await (C# et Visual Basic).

L'exemple suivant utilise les méthodes async pour télécharger le contenu d'un site Web spécifié en tant que chaîne et afficher la longueur de la chaîne. Cet exemple de code contient les deux méthodes suivantes :

  • startButton_Click, qui appelle AccessTheWebAsync et affiche le résultat.

  • AccessTheWebAsync, qui télécharge le contenu d'un site Web comme chaîne et retourne la longueur de la chaîne. AccessTheWebAsync utilise une méthode asynchrone HttpClient, GetStringAsync(String), pour télécharger le contenu.

Les lignes d'affichage numérotées apparaissent aux points stratégiques dans tout le programme pour vous aider à comprendre comment le programme s'exécute et à expliquer ce qui se produit à chaque point marqué. Les lignes d'affichage sont étiquetées « UN » à « SIX ». Les étiquettes représentent l'ordre dans lequel le programme atteint ces lignes de code.

Le code suivant illustre un plan du programme.

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

Ces emplacements étiquetés « UN » à « SIX » affiche des informations sur l'état actuel du programme. La sortie suivante est produite.

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.

Configurer le programme

Vous pouvez télécharger le code que cette rubrique utilise sur MSDN, ou vous pouvez le créer vous-même.

Notes

Pour exécuter l'exemple, Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012, Visual Studio Express 2013 pour Windows ou le .NET Framework 4.5 ou 4.5.1 doit être installé sur votre ordinateur.

Télécharger le programme

Vous pouvez télécharger l'application pour cette rubrique à partir de Exemple async : flux de contrôle dans les programmes async. Les étapes suivantes ouvrent et activent le programme.

  1. Décompressez le fichier téléchargé, puis démarrez Visual Studio.

  2. Dans la barre de menus, sélectionnez Fichier, Ouvrir, Projet/Solution.

  3. Accédez au dossier qui contient l'exemple de code dézippé, ouvrez le fichier solution (.sln), puis choisissez la touche F5 pour générer et exécuter le projet.

Générer le programme vous-même

Le projet Windows Presentation Foundation (WPF) suivant contient les exemples de code pour cette rubrique.

Pour exécuter le projet, exécutez les étapes suivantes :

  1. Démarrez Visual Studio.

  2. Dans la barre de menus, sélectionnez Fichier, Nouveau, Projet.

    La boîte de dialogue Nouveau projet s'affiche.

  3. Dans le volet Modèles installés, choisissez Visual Basic ou Visual C#, puis choisissez Application WPF dans la liste des types de projets.

  4. Entrez AsyncTracer comme nom du projet, puis cliquez sur OK.

    Le nouveau projet s'affiche dans l'Explorateur de solutions.

  5. Dans l'éditeur de code Visual Studio, choisissez l'onglet MainWindow.xaml.

    Si l'onglet n'est pas visible, ouvrez le menu contextuel de MainWindow.xaml dans l'Explorateur de solutions, puis choisissez Afficher le code.

  6. Dans la vue XAML de MainWindow.xaml, remplacez le code par le code suivant.

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

    Une fenêtre simple qui contient une zone de texte et un bouton apparaît dans la vue Design de MainWindow.xaml.

  7. Ajouter la référence System.Net.Http.

  8. Dans l'Explorateur de solutions, ouvrez le menu contextuel de MainWindow.xaml.vb ou MainWindow.xaml.cs, puis choisissez Afficher le Code.

  9. Dans MainWindow.xaml.vb ou MainWindow.xaml.cs, remplacez le code par le code suivant.

    ' 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. Choisissez la touche F5 pour exécuter le programme, puis choisissez le bouton Démarrer.

    La sortie suivante doit apparaître.

    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.
    

Tracer le programme

Étapes 1 et 2

Les deux premières lignes d'affichage suivent le chemin d'accès tandis que startButton_Click appelle AccessTheWebAsync, et AccessTheWebAsync appelle la méthode asynchrone HttpClient GetStringAsync(String). L'image suivante montre les appels de méthode à méthode.

Étapes UN et DEUX

Le type de retour de AccessTheWebAsync et de client.GetStringAsync est Task. Pour AccessTheWebAsync, TResult est un entier. Pour GetStringAsync, TResult est une chaîne. Pour plus d'informations sur les types de retour des méthodes async, consultez Types de retour Async (C# et Visual Basic).

Une méthode async retournant une tâche retourne une instance de tâche lorsque le contrôle se déplace vers l'appelant. Le contrôle retourne d'une méthode async à son appelant quand un opérateur Await ou await est rencontré dans la méthode appelée ou quand la méthode appelée se termine. Les lignes d'affichage qui sont étiquetées « TROIS » à « SIX » tracent cette partie du processus.

Étape 3

Dans AccessTheWebAsync, la méthode asynchrone GetStringAsync(String) est appelée pour télécharger le contenu de la page Web cible. Le contrôle retourne de client.GetStringAsync à AccessTheWebAsync lorsque client.GetStringAsync retourne.

La méthode client.GetStringAsync retourne une tâche de la chaîne assignée à la variable getStringTask dans AccessTheWebAsync. La ligne suivante dans le programme d'exemple illustre l'appel à client.GetStringAsync et l'assignation.

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

Vous pouvez considérer la tâche comme une promesse faite par client.GetStringAsync de finir par produire une chaîne réelle. Dans le même temps, si AccessTheWebAsync a du travail à effectuer qui ne dépend pas de la chaîne promise de client.GetStringAsync, ce travail peut se poursuivre tant que client.GetStringAsync attend. Dans l'exemple, les lignes de sortie, qui sont étiquetées « THREE, » représentent la possibilité d'effectuer le travail indépendant

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

L'instruction suivante interrompt la progression dans AccessTheWebAsync lorsque getStringTask est attendu.

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

L'image suivante montre l'ordre d'exécution entre client.GetStringAsync et l'affectation de getStringTask et entre la création de getStringTask et la demande d'un opérateur await.

Étape TROIS

L'expression await interrompt AccessTheWebAsync jusqu'à ce que client.GetStringAsync retourne. Dans le même temps, le contrôle retourne à l'appelant de AccessTheWebAsync, startButton_Click.

Notes

En général, vous attendez l'appel à une méthode asynchrone immédiatement.Par exemple, l'une des assignations suivantes peut substituer le code précédent qui crée, puis attend getStringTask :

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

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

Dans cette rubrique, l'opérateur d'attente est appliqué ultérieurement pour s'adapter aux lignes de sortie qui marquent l'ordre d'exécution du programme.

Étape 4

Le type de retour déclaré de AccessTheWebAsync est Task(Of Integer) en Visual Basic et Task<int> en C#. Par conséquent, lorsque AccessTheWebAsync est interrompu, il retourne une tâche d'entier à startButton_Click. Vous devez comprendre que la tâche retournée n'est pas getStringTask. La tâche retournée est une tâche de l'entier qui représente le reste à effectuer dans la méthode interrompue, AccessTheWebAsync. La tâche est une promesse de AccessTheWebAsync de produire un entier lorsque la tâche est terminée.

L'instruction suivante assigne cette tâche à la variable getLengthTask.

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

Comme dans AccessTheWebAsync, startButton_Click peut continuer le travail qui ne dépend pas des résultats de la tâche asynchrone (getLengthTask) jusqu'à ce que la tâche soit attendue. Les lignes de sortie suivantes représentent ce travail.

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

La progression dans startButton_Click est interrompue lorsque getLengthTask est attendu. L'instruction d'assignation suivante interrompt startButton_Click jusqu'à ce que AccessTheWebAsync soit terminé.

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

Dans l'illustration suivante, les flèches indiquent l'ordre d'exécution entre l'expression d'attente de AccessTheWebAsync et l'assignation d'une valeur à getLengthTask, suivie du traitement normal dans startButton_Click jusqu'à ce que getLengthTask soit attendu.

Étape QUATRE

Étape 5

Lorsque client.GetStringAsync signale qu'il est terminé, le traitement de AccessTheWebAsync est libéré de la suspension et peut continuer après l'instruction d'attente. Les lignes de sortie suivantes représentent la récupération du traitement.

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

L'opérande de l'instruction return, urlContents.Length, est stocké dans la tâche que AccessTheWebAsync retourne. L'expression await extrait la valeur de getLengthTask dans startButton_Click.

L'image suivante montre le transfert du contrôle après que client.GetStringAsync (et getStringTask) ont pris fin.

Étape CINQ

AccessTheWebAsync s'exécute jusqu'à la fin, et le contrôle retourne à startButton_Click, qui attend la fin.

Étape 6

Lorsque AccessTheWebAsync signale qu'il est terminé, le traitement peut continuer après l'instruction d'attente dans startButton_Async. En fait, le programme n'a rien à faire de plus.

Les lignes de sortie suivantes représentent la récupération du traitement dans 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'expression d'attente récupère de getLengthTask la valeur entière qui est l'opérande de l'instruction return dans AccessTheWebAsync. L'instruction suivante assigne la valeur à la variable contentLength.

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

L'image suivante montre le retour du contrôle de AccessTheWebAsync à startButton_Click.

Étape SIX

Voir aussi

Tâches

Procédure pas à pas : accès au Web avec Async et Await (C# et Visual Basic)

Procédure pas à pas : utilisation du débogueur avec les méthodes Async

Concepts

Programmation asynchrone avec Async et Await (C# et Visual Basic)

Types de retour Async (C# et Visual Basic)

Autres ressources

Exemple Async : Flux de contrôle dans les programmes Async (C# et Visual Basic)