Prise en main du débogage d’applications multithread (C#, Visual Basic et C++)

Visual Studio fournit plusieurs outils et éléments d’interface utilisateur qui aident à déboguer des applications multithread. Ce tutoriel montre comment utiliser les marqueurs de thread, la fenêtre Piles parallèles, la fenêtre Espion parallèle, les points d’arrêt conditionnels et les points d’arrêt de filtre. Vous vous familiarisez ainsi avec les fonctionnalités de débogage d’applications multithread de Visual Studio.

Les deux articles suivants fournissent des informations supplémentaires sur le recours à d’autres outils de débogage multithread :

La première étape consiste à créer un projet d’application multithread.

Création d’un projet d’application multithread

  1. Ouvrez Visual Studio et créez un projet.

    Si la fenêtre de démarrage n’est pas ouverte, choisissez Fichier>Fenêtre Démarrer.

    Dans la fenêtre de démarrage, choisissez Créer un projet.

    Dans la fenêtre Créer un projet, entrez ou tapez console dans la zone de recherche. Choisissez ensuite C#, C++ ou Visual Basic dans la liste des langages, puis choisissez Windows dans la liste des plateformes.

    Après avoir appliqué les filtres de langage et de plateforme, choisissez le modèle Application console pour .NET ou C++, puis choisissez Suivant.

    Note

    Si vous ne voyez pas le bon modèle, accédez à Outils>Obtenir des outils et fonctionnalités… pour ouvrir Visual Studio Installer. Choisissez la charge de travail Développement .NET Desktop ou Développement Desktop avec C++, puis choisissez Modifier.

    Dans la fenêtre Configurez votre nouveau projet, tapez ou entrez MyThreadWalkthroughApp dans la zone Nom du projet. Ensuite, choisissez Suivant ou Créer, selon l’option disponible.

    Pour un projet .NET Core ou .NET 5+, choisissez la version cible de .NET Framework recommandée ou .NET 8, puis choisissez Créer.

    Un nouveau projet console s'affiche. Une fois le projet créé, un fichier source apparaît. En fonction du langage choisi, le fichier source peut être nommé Program.cs, MyThreadWalkthroughApp.cpp ou Module1.vb.

  2. Supprimez le code qui apparaît dans le fichier source et remplacez-le par le code mis à jour suivant. Choisissez l’extrait de code approprié pour votre configuration de code.

    using System;
    using System.Threading;
    
    public class ServerClass
    {
    
        static int count = 0;
        // The method that will be called when the thread is started.
        public void InstanceMethod()
        {
            Console.WriteLine(
                "ServerClass.InstanceMethod is running on another thread.");
    
            int data = count++;
            // Pause for a moment to provide a delay to make
            // threads more apparent.
            Thread.Sleep(3000);
            Console.WriteLine(
                "The instance method called by the worker thread has ended. " + data);
        }
    }
    
    public class Simple
    {
        public static void Main()
        {
            for (int i = 0; i < 10; i++)
            {
                CreateThreads();
            }
        }
        public static void CreateThreads()
        {
            ServerClass serverObject = new ServerClass();
    
            Thread InstanceCaller = new Thread(new ThreadStart(serverObject.InstanceMethod));
            // Start the thread.
            InstanceCaller.Start();
    
            Console.WriteLine("The Main() thread calls this after "
                + "starting the new InstanceCaller thread.");
    
        }
    }
    
  3. Dans le menu Fichier, sélectionnez Enregistrer tout.

  4. (Visual Basic uniquement) Dans l’Explorateur de solutions (volet droit), cliquez avec le bouton droit sur le nœud du projet, puis choisissez Propriétés. Dans l’onglet Application, remplacez Objet de démarrage par Simple.

Débogage de l’application multithread

  1. Dans l’éditeur de code source, recherchez l’extrait de code suivant :

    Thread.Sleep(3000);
    Console.WriteLine();
    
  2. Cliquez avec le bouton gauche dans la marge de gauche de l’instruction Thread.Sleep ou, pour C++, std::this_thread::sleep_for pour insérer un nouveau point d’arrêt.

    Dans la marge, un cercle rouge indique qu’un point d’arrêt est défini à cet emplacement.

  3. Dans le menu Déboguer, sélectionnez Lancer le débogage (F5).

    Visual Studio génère la solution. L’application commence à s’exécuter avec le débogueur, puis s’arrête au point d’arrêt.

  4. Dans l’éditeur de code source, localisez la ligne contenant le point d'arrêt :

Découverte du marqueur de thread

  1. Dans la barre d’outils Déboguer, sélectionnez le bouton Afficher les threads dans la sourceAfficher les threads dans la source..

  2. Appuyez deux fois sur F11 pour faire avancer le débogueur.

  3. Examinez la reliure située sur le côté gauche de la fenêtre. Sur cette ligne, notez la présence d’une icône de Marqueur de threadmarqueur de thread qui ressemble à deux threads tordus. Le marqueur de thread indique qu'un thread est interrompu à cet emplacement.

    Un marqueur de thread peut être partiellement masqué par un point d’arrêt.

  4. Placez le pointeur sur le marqueur de thread. Un conseil sur les données apparaît, indiquant le nom et le numéro d’ID de chacun des threads arrêtés. Dans le cas présent, le nom est probablement <noname>.

    Capture d’écran de l’ID de thread dans DataTip.

  5. Sélectionnez le marqueur de thread pour afficher les options disponibles dans le menu contextuel.

Affichage de l’emplacement des threads

Dans la fenêtre Piles parallèles, vous pouvez basculer entre une vue Threads et (pour la programmation basée sur les tâches) une vue Tâches. Vous avez également la possibilité d’afficher les informations sur la pile des appels de chaque thread. Dans cette application, la vue Threads peut être utilisée.

  1. Ouvrez la fenêtre Piles parallèles en choisissant Déboguer>Fenêtres>Piles parallèles. La fenêtre se présente comme suit. Les informations exactes peuvent varier en fonction de l’emplacement actuel de chaque thread, du matériel et du langage de programmation.

    Capture d’écran de la fenêtre Piles parallèles.

    Dans cet exemple apparaissent les informations suivantes sur le code managé, de gauche à droite :

    • Le thread actuel (flèche jaune) a entré ServerClass.InstanceMethod. Vous pouvez afficher l’ID de thread et le frame de pile d’un thread en pointant sur ServerClass.InstanceMethod.
    • Le thread 31724 attend un verrou détenu par le thread 20272.
    • Le thread principal (à gauche) s’est arrêté sur [Code externe], que vous pouvez afficher en détail si vous choisissez Afficher le code externe.

    Fenêtre Piles parallèles.

    Dans cet exemple apparaissent les informations suivantes sur le code managé, de gauche à droite :

    • Le thread principal (côté gauche) s’est arrêté sur Thread.Start, où le point d’arrêt est identifié par l’icône de marqueur de thread Marqueur de thread.
    • Deux threads, dont le thread actuel (flèche jaune), sont entrés dans ServerClass.InstanceMethod, tandis que l’autre thread s’est arrêté dans Thread.Sleep.
    • Un nouveau thread (à droite) démarre également, mais s’arrête sur ThreadHelper.ThreadStart.
  2. Pour afficher les threads dans un affichage de liste, sélectionnez Déboguer>Windows>Threads.

    Capture d’écran de la fenêtre Threads.

    Dans cette vue, vous pouvez facilement voir que le thread 20272 est le thread principal et se trouve actuellement dans du code externe, en particulier System.Console.dll.

    Note

    Pour plus d’informations sur l’utilisation de la fenêtre Threads, consultez Procédure pas à pas : débogage d’une application multithread.

  3. Cliquez avec le bouton droit dans la fenêtre Piles parallèles ou Threads pour afficher les options disponibles dans le menu contextuel.

    Vous pouvez effectuer différentes actions à partir de ces menus contextuels. Pour ce tutoriel, vous allez explorer plus de détails dans la fenêtre Parallel Watch (sections suivantes).

Définition d’un espion dans une variable

  1. Ouvrez la fenêtre Espion parallèle en sélectionnant Déboguer>Fenêtres>Espion parallèle>Espion parallèle 1.

  2. Sélectionnez la cellule dans laquelle vous voyez le texte <Add Watch> (ou la cellule d’en-tête vide dans la quatrième colonne), puis entrez data.

    La valeur de la variable data de chaque thread s’affiche dans la fenêtre.

  3. Sélectionnez la cellule dans laquelle vous voyez le texte <Add Watch> (ou la cellule d’en-tête vide dans la quatrième colonne), puis entrez count.

    La valeur de la variable count de chaque thread s’affiche dans la fenêtre. Si vous ne voyez pas encore toutes ces informations, appuyez plusieurs fois sur F11 pour faire avancer l’exécution des threads dans le débogueur.

    Fenêtre Espion parallèle.

  4. Cliquez avec le bouton droit sur l’une des lignes de la fenêtre pour afficher les options disponibles.

Ajouter et supprimer des threads

Vous pouvez marquer des threads d’un indicateur pour effectuer le suivi des plus importants et ignorer les autres.

  1. Dans la fenêtre Espion parallèle, maintenez la touche Maj enfoncée et sélectionnez plusieurs lignes.

  2. Cliquez avec le bouton droit et sélectionnez Indicateur.

    Tous les threads sélectionnés sont marqués d’un indicateur. Vous pouvez à présent filtrer les threads pour afficher uniquement ceux qui sont marqués d’un indicateur.

  3. Dans la fenêtre Espion parallèle, sélectionnez le bouton Afficher uniquement les threads marqués d’un indicateurAfficher les threads marqués d’un indicateur..

    Seuls les threads marqués d’un indicateur apparaissent dans la liste.

    Conseil

    Une fois que vous avez marqué certains threads d’un indicateur, cliquez avec le bouton droit sur une ligne de code dans l’éditeur de code et choisissez Exécuter les threads marqués d’un indicateur au curseur. Veillez à choisir du code que tous les threads marqués d’un indicateur atteindront. Visual Studio interrompt les threads sur la ligne de code sélectionnée, ce qui permet de mieux contrôler l’ordre d’exécution en gelant et en libérant les threads.

  4. Sélectionnez à nouveau le bouton Afficher uniquement les threads marqués d’un indicateur pour revenir au mode Afficher tous les threads.

  5. Pour supprimer l’indicateur d’un ou de plusieurs threads, cliquez avec le bouton droit sur le ou les threads concernés dans la fenêtre Espion parallèle, puis sélectionnez Supprimer l’indicateur.

Gel et libération de l’exécution des threads

Conseil

Vous pouvez geler et libérer (suspendre et reprendre) des threads pour contrôler l’ordre dans lequel ils effectuent le travail. Cela peut vous aider à résoudre les problèmes d’accès concurrentiel, notamment les blocages et les conditions de concurrence.

  1. Dans la fenêtre Surveillance parallèle, sélectionnez toutes les lignes, cliquez avec le bouton droit et sélectionnez Geler.

    Dans la deuxième colonne, une icône de pause s’affiche pour chaque ligne. Elle indique que le thread est gelé.

  2. Désélectionnez toutes les autres lignes en sélectionnant une seule ligne.

  3. Cliquez avec le bouton droit sur une ligne et sélectionnez Libérer.

    L’icône de pause disparaît sur cette ligne, ce qui indique que le thread n’est plus gelé.

  4. Basculez vers l’éditeur de code et appuyez sur F11. Seul le thread libéré s’exécute.

    Il se peut aussi que l’application instancie de nouveaux threads. Ceux-ci ne sont ni marqués d’un indicateur, ni gelés.

Suivi d’un seul thread avec des points d’arrêt conditionnels

Il est parfois utile de suivre l’exécution d’un seul thread dans le débogueur. Pour ce faire, vous pouvez geler les threads qui ne vous intéressent pas. Certains scénarios imposent toutefois de suivre un seul thread sans geler les autres, par exemple pour reproduire un bogue en particulier. Vous devez alors éviter d’entrer dans le code en dehors du thread qui vous intéresse. Définissez pour cela un point d’arrêt conditionnel.

Vous pouvez définir des points d’arrêt sur différentes conditions, notamment le nom du thread ou son ID. Il peut être utile de spécifier la condition sur des données dont vous savez qu’elles sont propres à chaque thread. Cette approche est courante lors du débogage lorsque vous êtes plus intéressé par une valeur de données particulière que par n’importe quel thread particulier.

  1. Cliquez avec le bouton droit sur le point d’arrêt que vous avez créé, puis sélectionnez Conditions.

  2. Dans la fenêtre Paramètres du point d’arrêt, entrez data == 5 comme expression conditionnelle.

    Point d’arrêt conditionnel.

    Conseil

    Si vous vous intéressez plutôt à un thread spécifique, utilisez son nom ou son ID comme condition. Pour ce faire, sélectionnez Filtre au lieu d’Expression conditionnelle dans la fenêtre Paramètres du point d’arrêt, puis suivez les conseils de filtre. Il peut être judicieux de nommer vos threads dans le code de votre application, car les ID de threads changent lorsque vous redémarrez le débogueur.

  3. Fermez la fenêtre Paramètres du point d’arrêt.

  4. Sélectionnez le bouton Redémarrer Redémarrer l’application. pour relancer votre session de débogage.

    Vous entrez dans le code sur le thread à l’endroit où la valeur de la variable de données est 5. Dans la fenêtre Surveillance parallèle, recherchez la flèche jaune indiquant le contexte actuel du débogueur.

  5. Vous pouvez maintenant exécuter le code pas à pas (F10) et entrer dans le code (F11) pour suivre l’exécution du thread unique.

    Tant que la condition de point d’arrêt est propre au thread et que le débogueur n’atteint pas d’autres points d’arrêt sur d’autres threads (vous devrez peut-être les désactiver), vous pouvez exécuter le code pas à pas et entrer dans le code sans basculer vers d’autres threads.

    Remarque

    Tous les threads s’exécutent lorsque vous faites avancer le débogueur. Toutefois, celui-ci n’entre dans le code sur d’autres threads que si l’un d’entre eux atteint un point d’arrêt.