Erste Schritte zum Debuggen von Multithreadanwendungen (C#, Visual Basic, C++)

Visual Studio bietet verschiedene Tools und Benutzeroberflächenelemente, die Sie beim Debuggen von Multithreadanwendungen unterstützen. In diesem Tutorial wird gezeigt, wie Threadmarker, das Fenster Parallele Stapel, das Fenster Parallele Überwachung, bedingte Haltepunkte und Filterhaltepunkte verwendet werden. Nach Abschluss dieses Tutorials sind Sie mit den Visual Studio-Funktionen zum Debuggen von Multithreadanwendungen vertraut.

Diese beiden Artikel enthalten zusätzliche Informationen zur Verwendung anderer Multithread-Debuggingtools:

Im ersten Schritt wird ein Projekt für eine Multithreadanwendung erstellt.

Erstellen eines Multithreadanwendungs-Projekts

  1. Öffnen Sie Visual Studio, und erstellen Sie ein neues Projekt.

    Wenn das Startfenster nicht geöffnet ist, klicken Sie auf Datei >Startfenster.

    Wählen Sie im Startfenster Neues Projekt erstellen aus.

    Geben Sie im Fenster Neues Projekt erstellen im Suchfeld Konsole ein. Wählen Sie anschließend in der Liste mit den Sprachen die Option C# , C++ oder Visual Basic und dann in der Liste mit den Plattformen Windows aus.

    Nachdem Sie die Sprach- und Plattformfilter angewendet haben, wählen Sie die Vorlage Konsolen-App für .NET Core oder C++ und dann Weiter aus.

    Hinweis

    Wenn die richtige Vorlage nicht angezeigt wird, öffnen Sie unter Tools>Tools und Features abrufen... den Visual Studio-Installer. Wählen Sie die Workload .NET-Desktopentwicklung oder Desktopentwicklung mit C++ und anschließend Ändern aus.

    Geben Sie im Fenster Neues Projekt konfigurieren im Feld ProjektnameMyThreadWalkthroughApp ein. Klicken Sie anschließend je nach verfügbarer Option entweder auf Weiter oder auf Erstellen.

    Wählen Sie für ein .NET Core- oder .NET 5+-Projekt entweder das empfohlene Zielframework oder .NET 8 aus, und klicken Sie dann auf Erstellen.

    Ein neues Konsolenprojekt wird angezeigt. Nachdem das Projekt erstellt wurde, wird eine Quelldatei angezeigt. Abhängig von der ausgewählten Programmiersprache trägt die Quelldatei ggf. den Namen Program.cs, MyThreadWalkthroughApp.cpp oder Module1.vb.

  2. Löschen Sie den Code, der in der Quelldatei angezeigt wird, und ersetzen Sie ihn durch den folgenden aktualisierten Code. Wählen Sie den entsprechenden Codeschnipsel für Ihre Codekonfiguration aus.

    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. Wählen Sie im Menü Datei den Befehl Alle speichern aus.

  4. (Nur Visual Basic) Klicken Sie in Projektmappen-Explorer (rechter Bereich) mit der rechten Maustaste auf den Projektknoten, und wählen Sie Eigenschaften aus. Ändern Sie auf der Registerkarte Anwendung das Startobjekt in Simple.

Debuggen einer Multithreadanwendung

  1. Suchen Sie im Quellcode-Editor nach einem der folgenden Codeschnipsel:

    Thread.Sleep(3000);
    Console.WriteLine();
    
  2. Klicken Sie mit der linken Maustaste auf den linken Bundsteg der Anweisung Thread.Sleep oder std::this_thread::sleep_for (für C++), um einen neuen Breakpoint einzufügen.

    In diesem Bundsteg wird durch einen roten Kreis angezeigt, dass an dieser Stelle jetzt ein Haltepunkt festgelegt ist.

  3. Klicken Sie im Menü Debuggen auf Debuggen starten (F5).

    Visual Studio erstellt die Projektmappe, die Anwendung wird mit dem angefügten Debugger ausgeführt, und die Anwendung wird am Haltepunkt angehalten.

  4. Suchen Sie im Quellcode-Editor die Zeile, die den Haltepunkt enthält.

Ermitteln des Threadmarkers

  1. Wählen Sie auf der Symbolleiste „Debuggen“ die Schaltfläche Threads in Quelle anzeigen aus Show Threads in Source.

  2. Drücken Sie zweimal F11, um den Debugger voranzutreiben.

  3. Betrachten Sie den Bundsteg auf der linken Seite des Fensters. In dieser Zeile sehen Sie ein Threadmarker-Symbol Thread Marker, das zwei verdrillten Fäden ähnelt. Der Threadmarker gibt an, dass ein Thread an dieser Position angehalten wurde.

    Ein Threadmarker kann teilweise durch einen Breakpoint verdeckt sein.

  4. Zeigen Sie mit dem Mauszeiger auf den Threadmarker. Anhand eines angezeigten DataTips erfahren Sie den Namen und die Thread-ID jedes angehaltenen Threads. In diesem Fall ist der Name wahrscheinlich <noname>.

    Screenshot of the Thread ID in a DataTip.

  5. Wählen Sie den Threadmarker aus, um die verfügbaren Optionen im Kontextmenü anzuzeigen.

Anzeigen der Threadpositionen

Im Fenster Parallele Stapel können Sie zwischen einer Threadsansicht und (für die taskbasierte Programmierung) der Tasksansicht wechseln, und Sie können Aufrufstapelinformationen für jeden Thread anzeigen. In dieser App können wir die Threadansicht verwenden.

  1. Öffnen Sie das Fenster Parallele Stapel, indem Sie Debuggen>Fenster>Parallele Stapel wählen. Die Anzeige sollte dem Folgenden ähneln: Die genauen Informationen hängen von der aktuellen Position jedes Threads, Ihrer Hardware und Ihrer Programmiersprache ab.

    Screenshot of the Parallel Stacks Window.

    In diesem Beispiel sind diese Informationen für verwalteten Code von links nach rechts dargestellt:

    • Der aktuelle Thread (gelber Pfeil) hat ServerClass.InstanceMethod eingegeben. Sie können die Thread-ID und den Stapelrahmen eines Threads anzeigen, indem Sie auf ServerClass.InstanceMethod zeigen.
    • Thread 31724 wartet auf eine Sperre im Besitz von Thread 20272.
    • Der Hauptthread (links) wurde auf [Externer Code] beendet, den Sie im Detail anzeigen können, wenn Sie Externen Code anzeigen auswählen.

    Parallel Stacks Window

    In diesem Beispiel sind diese Informationen für verwalteten Code von links nach rechts dargestellt:

    • Der Hauptthread (linke Seite) hat bei Thread.Start angehalten, wobei der Endpunkt durch das Threadmarkersymbol Thread Marker angezeigt wird.
    • Zwei Threads haben ServerClass.InstanceMethod eingegeben, von denen einer der aktuelle Thread (gelber Pfeil) ist, während der andere Thread in Thread.Sleep angehalten wurde.
    • Ein neuer Thread (auf der rechten Seite) wird ebenfalls gestartet, aber bei ThreadHelper.ThreadStart angehalten.
  2. Um die Threads in einer Listenansicht anzuzeigen, wählen Sie Debuggen>Windows>Threads aus.

    Screenshot of the Threads Window.

    In dieser Ansicht können Sie leicht erkennen, dass Thread 20272 der Hauptthread ist und sich derzeit in externem Code befindet, insbesondere System.Console.dll.

    Hinweis

    Weitere Informationen zur Verwendung des Fensters Threads finden Sie unter Exemplarische Vorgehensweise: Debuggen einer Multithreadanwendung.

  3. Klicken Sie mit der rechten Maustaste auf Einträge im Fenster Parallele Stapel oder Threads, um die verfügbaren Optionen im Kontextmenü anzuzeigen.

    Sie können verschiedene Aktionen über diese Kontextmenüs ausführen. In diesem Tutorial erfahren Sie mehr über diese Details im Fenster Parallele Überwachung (nächste Abschnitte).

Festlegen eines Überwachungselements für eine Variable

  1. Öffnen Sie das Fenster Parallele Überwachung, indem Sie Debuggen>Fenster>Parallele Überwachung>Parallele Überwachung 1 auswählen.

  2. Wählen Sie die Zelle aus, in der Sie den Text <Add Watch> (oder die leere Kopfzelle in der 4. Spalte) sehen, und geben Sie data ein.

    Die Werte für die Datenvariable für jeden Thread werden im Fenster angezeigt.

  3. Wählen Sie die Zelle aus, in der Sie den Text <Add Watch> (oder die leere Kopfzelle in der 4. Spalte) sehen, und geben Sie count ein.

    Die Werte für die Variable count für jeden Thread werden im Fenster angezeigt. Wenn noch nicht so viele Informationen angezeigt werden, drücken Sie mehrmals F11, um die Ausführung der Threads im Debugger zu beschleunigen.

    Parallel Watch Window

  4. Klicken Sie mit der rechten Maustaste auf eine der Zeilen im Fenster, um die verfügbaren Optionen anzuzeigen.

Kennzeichnen von Threads und Aufheben der Kennzeichnung

Sie können Threads markieren, um wichtige Threads zu verfolgen und die anderen Threads zu ignorieren.

  1. Halten Sie im Fenster Parallele Überwachung die UMSCHALTTASTE gedrückt, und wählen Sie mehrere Zeilen aus.

  2. Klicken Sie mit der rechten Maustaste, und wählen Sie Kennzeichnen aus.

    Alle ausgewählten Threads werden gekennzeichnet. Nun können Sie nach gekennzeichneten Threads filtern.

  3. Wählen Sie im Fenster Parallele Überwachung die Schaltfläche Nur gekennzeichnete Threads anzeigen aus Show Flagged Threads.

    Anschließend wird nur der gekennzeichnete Thread in der Liste angezeigt.

    Tipp

    Nachdem Sie einige Threads gekennzeichnet haben, können Sie im Code-Editor mit der rechten Maustaste auf eine Codezeile klicken und Threads bis zum Cursor ausführen auswählen. Stellen Sie sicher, dass Sie Code auswählen, der von allen gekennzeichneten Threads erreicht wird. Visual Studio hält Threads in der ausgewählten Codezeile an, um die Ausführungsreihenfolge durch Einfrieren und Reaktivieren von Threads einfacher zu steuern.

  4. Klicken Sie erneut auf die Schaltfläche Nur gekennzeichnete Threads anzeigen, um zurück zum Modus Alle Threads anzeigen zu wechseln.

  5. Um die Kennzeichnung von Threads aufzuheben, klicken Sie mit der rechten Maustaste auf einen oder mehrere markierte Threads im Fenster Parallele Überwachung und wählen Sie Kennzeichnung aufheben.

Einfrieren und Reaktivieren der Threadausführung

Tipp

Sie können Threads einfrieren und reaktivieren (anhalten und fortsetzen), um die Reihenfolge zu steuern, in der die Threads Aufgaben ausführen. Dies kann Ihnen helfen, Parallelitätsprobleme zu beheben, z. B. Deadlocks und Racebedingungen.

  1. Klicken Sie im Fenster Parallele Überwachung, während alle Zeilen ausgewählt sind, mit der rechten Maustaste, und wählen Sie Einfrieren aus.

    In der zweiten Spalte wird für jede Zeile ein Pausensymbol angezeigt. Das Pausensymbol gibt an, dass der Thread eingefroren ist.

  2. Deaktivieren Sie alle anderen Zeilen, indem Sie nur eine Zeile auswählen.

  3. Klicken Sie mit der rechten Maustaste auf eine Zeile, und wählen Sie Reaktivieren aus.

    Das Pausensymbol wird in dieser Zeile entfernt, was bedeutet, dass der Thread nicht mehr eingefroren ist.

  4. Wechseln Sie zum Code-Editor, und drücken Sie F11. Es wird nur der nicht eingefrorene Thread ausgeführt.

    Die App instanziiert möglicherweise auch einige neue Threads. Alle neuen Threads sind nicht gekennzeichnet und nicht eingefroren.

Folgen eines einzelnen Threads mit bedingten Haltepunkten

Es kann hilfreich sein, der Ausführung eines einzelnen Threads im Debugger zu folgen. Eine Methode, um dies zu erreichen, ist das Einfrieren von Threads, an denen Sie nicht interessiert sind. In einigen Szenarien müssen Sie möglicherweise einem einzelnen Thread folgen, ohne andere Threads einzufrieren, z. B. um einen bestimmten Fehler zu reproduzieren. Wenn Sie einem Thread folgen möchten, ohne andere Threads einzufrieren, müssen Sie das Unterbrechen von Code vermeiden, außer in dem Thread, an dem Sie interessiert sind. Dies können Sie erreichen, indem Sie einen bedingten Haltepunkt festlegen.

Sie können Haltepunkte für verschiedene Bedingungen festlegen, z. B. den Threadnamen oder die Thread-ID. Es kann hilfreich sein, die Bedingung für Daten festzulegen, von der Sie wissen, dass sie für jeden Thread eindeutig ist. Dies ist ein gängiger Debugansatz, bei dem Sie mehr an einem bestimmten Datenwert als an einem bestimmten Thread interessiert sind.

  1. Klicken Sie mit der rechten Maustaste auf den zuvor erstellten Haltepunkt und dann auf Bedingungen.

  2. Geben Sie im Fenster Haltepunkteinstellungen für den bedingten Ausdruck data == 5 ein.

    Conditional Breakpoint

    Tipp

    Wenn Sie mehr an einem bestimmten Thread interessiert sind, dann verwenden Sie einen Threadnamen oder eine Thread-ID für die Bedingung. Wählen Sie dazu im Fenster Haltepunkteinstellungen die Option Filter anstelle von Bedingter Ausdruck aus, und folgen Sie den Filtertipps. Sie sollten Ihre Threads im Anwendungscode benennen, da sich die Thread-IDs ändern, wenn Sie den Debugger neu starten.

  3. Schließen Sie das Fenster Haltepunkteinstellungen.

  4. Wählen Sie die Schaltfläche Restart App aus, um Ihre Debugsitzung neu zu starten.

    Sie unterbrechen den Code im Thread, bei dem der Wert der Datenvariablen 5 ist. Suchen Sie im Fenster Parallele Überwachung nach dem gelben Pfeil, der den aktuellen Debuggerkontext anzeigt.

  5. Nun können Sie Code überspringen (F10), Code schrittweise ausführen (F11) und der Ausführung des einzelnen Threads folgen.

    Solange die Haltepunktbedingung für den Thread eindeutig ist und der Debugger auf keine anderen Haltepunkte in anderen Threads trifft (ggf. müssen Sie diese deaktivieren), können Sie Code überspringen und Code schrittweise ausführen, ohne zu anderen Threads zu wechseln.

    Hinweis

    Wenn Sie den Debugger fortsetzen, werden alle Threads ausgeführt. Der Debugger unterbricht jedoch keinen Code in anderen Threads, es sei denn, einer der anderen Threads erreicht einen Haltepunkt.