Note
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier les répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de changer de répertoire.
Ce tutoriel montre comment utiliser la vue Threads des fenêtres Parallel Stacks pour déboguer une application multithreadée. Cette fenêtre vous aide à comprendre et à vérifier le comportement d’exécution du code multithread.
La vue Threads est prise en charge pour C#, C++ et Visual Basic. L’exemple de code est fourni pour C# et C++, mais certaines références et illustrations de code s’appliquent uniquement à l’exemple de code C#.
La vue Threads vous aide à :
Affichez les visualisations de pile d’appels pour plusieurs threads, qui fournit une image plus complète de l’état de votre application que la fenêtre Pile des appels, qui affiche simplement la pile des appels pour le thread actuel.
Aidez à identifier les problèmes tels que les threads bloqués ou verrouillés.
Piles d’appels multithread
Les sections identiques de la pile des appels sont regroupées pour simplifier la visualisation des applications complexes.
L’animation conceptuelle suivante montre comment le regroupement est appliqué aux piles d’appels. Seuls les segments identiques d’une pile d’appels sont regroupés. Placez le curseur sur une pile d’appels groupée pour idenitfy les threads.
Vue d’ensemble de l’exemple de code (C#, C++)
L’exemple de code de cette procédure pas à pas est destiné à une application qui simule un jour dans la vie d’un gorille. L’objectif de l’exercice est de comprendre comment utiliser la vue des Threads de la fenêtre Piles Parallèles pour déboguer une application multithreadée.
L’exemple inclut un cas d’interblocage, qui se produit lorsque deux threads attendent l'un l'autre.
Pour rendre la pile des appels intuitive, l’exemple d’application effectue les étapes séquentielles suivantes :
- Crée un objet représentant un gorille.
- Gorille se réveille.
- Le gorille fait une promenade matinale.
- Gorille trouve des bananes dans la jungle.
- Gorille mange.
- Gorilla se livre à des manigances.
Créer l’exemple de projet
Pour créer le projet :
Ouvrez Visual Studio et créez un projet.
Si la fenêtre de démarrage n’est pas ouverte, choisissez Fichier>fenêtre de démarrage.
Dans la fenêtre Démarrer, choisissez Nouveau projet.
Dans la fenêtre Créer un projet , entrez ou tapez la console dans la zone de recherche. Ensuite, choisissez C# ou C++ dans la liste de langues, puis choisissez Windows dans la liste plateforme.
Après avoir appliqué les filtres de langue et de plateforme, choisissez l’application console pour votre langue choisie, puis choisissez Suivant.
Note
Si vous ne voyez pas le modèle approprié, accédez à ToolsGet Tools >and Features..., qui ouvre Visual Studio Installer. Choisissez la charge de travail Développement .NET Desktop, puis choisissez Modifier.
Dans la fenêtre Configurer votre nouveau projet , tapez un nom ou utilisez le nom par défaut dans la zone Nom du projet . Choisissez ensuite Suivant.
Pour un projet .NET, choisissez le framework cible recommandé ou .NET 8, puis choisissez Créer.
Un nouveau projet console s'affiche. Une fois le projet créé, un fichier source s’affiche.
Ouvrez le fichier de code .cs (ou .cpp) dans le projet. Supprimez son contenu pour créer un fichier de code vide.
Collez le code suivant pour votre langue choisie dans le fichier de code vide.
using System.Diagnostics; namespace Multithreaded_Deadlock { class Jungle { public static readonly object tree = new object(); public static readonly object banana_bunch = new object(); public static Barrier barrier = new Barrier(2); public static int FindBananas() { // Lock tree first, then banana lock (tree) { lock (banana_bunch) { Console.WriteLine("Got bananas."); return 0; } } } static void Gorilla_Start(object lockOrderObj) { Debugger.Break(); bool lockTreeFirst = (bool)lockOrderObj; Gorilla koko = new Gorilla(lockTreeFirst); int result = 0; var done = new ManualResetEventSlim(false); Thread t = new Thread(() => { result = koko.WakeUp(); done.Set(); }); t.Start(); done.Wait(); } static void Main(string[] args) { List<Thread> threads = new List<Thread>(); // Start two threads with opposite lock orders threads.Add(new Thread(Gorilla_Start)); threads[0].Start(true); // First gorilla locks tree then banana threads.Add(new Thread(Gorilla_Start)); threads[1].Start(false); // Second gorilla locks banana then tree foreach (var t in threads) { t.Join(); } } } class Gorilla { private readonly bool lockTreeFirst; public Gorilla(bool lockTreeFirst) { this.lockTreeFirst = lockTreeFirst; } public int WakeUp() { int myResult = MorningWalk(); return myResult; } public int MorningWalk() { Debugger.Break(); if (lockTreeFirst) { lock (Jungle.tree) { Jungle.barrier.SignalAndWait(5000); // For thread timing consistency in sample Jungle.FindBananas(); GobbleUpBananas(); } } else { lock (Jungle.banana_bunch) { Jungle.barrier.SignalAndWait(5000); // For thread timing consistency in sample Jungle.FindBananas(); GobbleUpBananas(); } } return 0; } public void GobbleUpBananas() { Console.WriteLine("Trying to gobble up food..."); DoSomeMonkeyBusiness(); } public void DoSomeMonkeyBusiness() { Thread.Sleep(1000); Console.WriteLine("Monkey business done"); } } }Dans le menu Fichier, sélectionnez Enregistrer tout.
Dans le menu Générer, sélectionnez Générer la solution.
Utiliser la vue Threads de la fenêtre Piles parallèles
Pour démarrer le débogage :
Dans le menu Débogage , sélectionnez Démarrer le débogage (ou F5) et attendez que le premier
Debugger.Break()soit atteint.Note
En C++, le débogueur s’interrompt en
__debug_break(). Le reste des références de code et des illustrations de cet article concernent la version C#, mais les mêmes principes de débogage s’appliquent à C++.Appuyez une fois sur F5 et le débogueur s’interrompt à nouveau sur la même
Debugger.Break()ligne.Cette opération s'interrompt lors du deuxième appel à
Gorilla_Start, qui se produit au sein d’un deuxième thread.Tip
Le débogueur interrompt l'exécution du code de manière individuelle pour chaque thread. Par exemple, cela signifie que si vous appuyez sur F5 pour poursuivre l’exécution et que l’application atteint le prochain point d’arrêt, elle peut passer dans le code d'un autre thread d'exécution. Si vous devez gérer ce comportement à des fins de débogage, vous pouvez ajouter des points d’arrêt supplémentaires, des points d’arrêt conditionnels ou utiliser Break All. Pour plus d’informations sur l’utilisation de points d’arrêt conditionnels, consultez Suivre un thread unique avec des points d’arrêt conditionnels.
Sélectionnez Déboguer > Fenêtres > Piles parallèles pour ouvrir la fenêtre Piles parallèles, puis sélectionnez Threads dans la liste déroulante Vue dans la fenêtre.
Dans la vue Threads, le frame de pile et le chemin d’appel du thread actuel sont mis en surbrillance en bleu. L’emplacement actuel du thread est affiché par la flèche jaune.
Remarquez que l'étiquette de la pile des appels pour
Gorilla_Startest 2 threads. Lorsque vous avez appuyé sur F5 pour la dernière fois, vous avez démarré un autre thread. Pour simplifier les applications complexes, les piles d’appels identiques sont regroupées en une représentation visuelle unique. Cela simplifie les informations potentiellement complexes, en particulier dans les scénarios avec de nombreux threads.Pendant le débogage, vous pouvez choisir d’afficher ou de masquer le code externe. Pour activer/désactiver la fonctionnalité, sélectionnez ou désactivez Afficher le code externe. Si vous affichez du code externe, vous pouvez toujours utiliser cette procédure pas à pas, mais vos résultats peuvent différer des illustrations.
Appuyez à nouveau sur F5 , puis le débogueur s’interrompt dans la
Debugger.Break()ligne de laMorningWalkméthode.La fenêtre des Stacks parallèles montre l'emplacement du thread actuellement exécuté dans la méthode
MorningWalk.
Pointez sur la méthode
MorningWalkpour obtenir des informations sur les deux threads représentés par la pile des appels groupés.Le thread actuel apparaît également dans la liste Thread de la barre d’outils de débogage.
Vous pouvez utiliser la liste des threads pour basculer le contexte du débogueur vers un autre thread. Cela ne modifie pas le thread en cours d’exécution, simplement le contexte du débogueur.
Vous pouvez également basculer le contexte du débogueur en double-cliquant sur une méthode dans la vue Threads, ou en cliquant avec le bouton droit sur une méthode dans la vue Threads et en sélectionnant Basculer vers frame>[ID de thread].
Appuyez à nouveau sur F5 et le débogueur se met en pause dans la méthode
MorningWalkdu deuxième thread.
Selon le minutage de l’exécution du thread, à ce stade, vous voyez des piles d’appels distinctes ou groupées.
Dans l’illustration précédente, les piles d’appels des deux threads sont regroupées partiellement. Les segments identiques des piles d’appels sont regroupés et les lignes de flèche pointent vers les segments séparés (autrement dit, pas identiques). Le cadre de la pile actuelle est indiqué par une surbrillance bleue.
Appuyez à nouveau sur F5 , et vous verrez un long délai se produire et la vue Threads n’affiche aucune information de pile d’appels.
Le délai est dû à un blocage. Rien n’apparaît dans la vue Threads, car même si les threads peuvent être bloqués, vous n’êtes pas actuellement suspendu dans le débogueur.
Note
En C++, vous voyez également une erreur de débogage indiquant qu’elle
abort()a été appelée.Tip
Le bouton Arrêter tout est un bon moyen d’obtenir des informations sur la pile des appels si un blocage se produit ou que tous les threads sont actuellement bloqués.
En haut de l’IDE dans la barre d’outils Débogage, sélectionnez le bouton Arrêter tout (icône pause) ou utilisez Ctrl + Alt + Arrêt.
Le sommet de la pile des appels dans la vue des Threads montre que
FindBananasest en impasse. Le pointeur d’exécution dansFindBananasest une flèche verte incurvée, indiquant le contexte actuel du débogueur et précisant également que les threads ne sont pas en cours d’exécution.Note
En C++, vous ne voyez pas l'information et les icônes utiles indiquant « deadlock detected ». Toutefois, vous trouvez toujours la flèche verte bouclée dans
Jungle.FindBananas, indiquant l'emplacement du blocage.Dans l’éditeur de code, nous trouvons la flèche verte bouclée dans la
lockfonction. Les deux threads sont bloqués sur la fonctionlockdans la méthodeFindBananas.Selon l’ordre d’exécution du fil d'exécution, le blocage apparaît soit dans l’instruction
lock(tree), soit dans l’instructionlock(banana_bunch).L'appel à
lockbloque les threads de la méthodeFindBananas. Un thread attend que le verrou soittreelibéré par l’autre thread, mais l’autre thread attend que le verrou soitbanana_bunchlibéré avant de pouvoir libérer le verroutree. Il s’agit d’un exemple d’interblocage classique qui se produit lorsque deux threads attendent les uns les autres.Si vous utilisez Copilot, vous pouvez également obtenir des résumés de threads générés par l’IA pour vous aider à identifier les blocages potentiels.
Corriger l’exemple de code
Pour corriger ce code, achetez toujours plusieurs verrous dans un ordre global cohérent entre tous les threads. Cela empêche les attentes circulaires et élimine les interblocages.
Pour corriger le blocage, remplacez le code dans
MorningWalkpar le code suivant.public int MorningWalk() { Debugger.Break(); // Always lock tree first, then banana_bunch lock (Jungle.tree) { Jungle.barrier.SignalAndWait(5000); // OK to remove lock (Jungle.banana_bunch) { Jungle.FindBananas(); GobbleUpBananas(); } } return 0; }Redémarrez l’application.
Summary
Cette procédure pas à pas a présenté la fenêtre du débogueur Piles parallèles. Utilisez cette fenêtre pour des projets réels utilisant du code multi-thread. Vous pouvez examiner le code parallèle écrit en C++, C# ou Visual Basic.