Introduzione al debug di applicazioni multithreading (C#, Visual Basic, C++)

Visual Studio offre diversi strumenti ed elementi dell'interfaccia utente che consentono di eseguire il debug di applicazioni multithreading. Questa esercitazione illustra come usare i marcatori di thread, la finestra Stack paralleli , la finestra Espressione di controllo parallelo, i punti di interruzione condizionali e i punti di interruzione del filtro. Il completamento di questa esercitazione consente di acquisire familiarità con le funzionalità di Visual Studio per il debug di applicazioni multithreading.

Questi due articoli forniscono informazioni aggiuntive sull'uso di altri strumenti di debug multithreading:

Il primo passaggio consiste nel creare un progetto di applicazione multithreading.

Creare un progetto di app multithreading

  1. Aprire Visual Studio e creare un nuovo progetto.

    Se la finestra iniziale non è aperta, scegliere Finestra di avvio file>.

    Nella finestra iniziale scegliere Crea un nuovo progetto.

    Nella finestra Crea un nuovo progetto immettere o digitare console nella casella di ricerca. Scegliere quindi C#, C++o Visual Basic dall'elenco Linguaggio e quindi scegliere Windows dall'elenco Piattaforma.

    Dopo aver applicato i filtri del linguaggio e della piattaforma, scegliere il modello App console per .NET o C++, quindi scegliere Avanti.

    Nota

    Se il modello corretto non è visualizzato, passare a Strumenti Ottieni strumenti>e funzionalità, che apre il Programma di installazione di Visual Studio. Scegliere il carico di lavoro Sviluppo per desktop .NET o Sviluppo di applicazioni desktop con C++, quindi scegliere Modifica.

    Nella finestra Configura il nuovo progetto digitare o immettere MyThreadWalkthroughApp nella casella Nome progetto. Scegliere quindi Avanti o Crea, a qualsiasi opzione disponibile.

    Per un progetto .NET Core o .NET 5+, scegliere il framework di destinazione consigliato o .NET 8 e quindi scegliere Crea.

    Verrà visualizzato un nuovo progetto console. Dopo aver creato il progetto, viene visualizzato un file di origine. A seconda della lingua scelta, il file di origine potrebbe essere denominato Program.cs, MyThreadWalkthroughApp.cpp o Module1.vb.

  2. Eliminare il codice visualizzato nel file di origine e sostituirlo con il codice aggiornato seguente. Scegliere il frammento di codice appropriato per la configurazione del codice.

    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. Scegliere Save All (Salva tutto) dal menu File.

  4. (solo Visual Basic) In Esplora soluzioni (riquadro destro) fare clic con il pulsante destro del mouse sul nodo del progetto, scegliere Proprietà. Nella scheda Applicazione modificare l'oggetto Startup in Simple.

Eseguire il debug dell'app multithreading

  1. Nell'editor del codice sorgente cercare il frammento di codice seguente:

    Thread.Sleep(3000);
    Console.WriteLine();
    
  2. Fare clic con il pulsante sinistro del mouse sulla barra sinistra dell'istruzione Thread.Sleep o, per C++, std::this_thread::sleep_for per inserire un nuovo punto di interruzione.

    Nella barra, un cerchio rosso indica che un punto di interruzione è impostato in questa posizione.

  3. Nel menu Debug selezionare Avvia debug (F5).

    Visual Studio compila la soluzione, l'app inizia a essere eseguita con il debugger collegato e quindi l'app si arresta in corrispondenza del punto di interruzione.

  4. Nell'editor del codice sorgente individuare la riga contenente il punto di interruzione.

Individuare il marcatore del thread

  1. Nella barra degli strumenti debug selezionare il pulsante Show Threads in SourceMostra thread nell'origine .

  2. Premere F11 due volte per far avanzare il debugger.

  3. All'estrema sinistra della finestra, In questa riga si noti un'icona del Thread Marker marcatore di thread simile a due thread contorto. Il marcatore del thread indica l'interruzione di un thread in questa posizione.

    Un marcatore di thread può essere parzialmente nascosto da un punto di interruzione.

  4. Posizionare il puntatore del mouse sul marcatore del thread. Viene visualizzato un suggerimento dati che indica il nome e il numero ID del thread per ogni thread arrestato. In questo caso, il nome è probabilmente <noname>.

    Screenshot of the Thread ID in a DataTip.

  5. Selezionare l'indicatore di thread per visualizzare le opzioni disponibili nel menu di scelta rapida.

Visualizzare i percorsi dei thread

Nella finestra Stack paralleli è possibile passare da una visualizzazione Thread a una visualizzazione Thread e (per la programmazione basata su attività) Attività ed è possibile visualizzare le informazioni sullo stack di chiamate per ogni thread. In questa app è possibile usare la visualizzazione Thread.

  1. Aprire la finestra Stack paralleli scegliendo Debug>stack paralleli di Windows.> Dovrebbe essere visualizzato un aspetto simile al seguente. Le informazioni esatte possono variare a seconda della posizione corrente di ogni thread, dell'hardware e del linguaggio di programmazione.

    Screenshot of the Parallel Stacks Window.

    In questo esempio, da sinistra a destra vengono visualizzate queste informazioni per il codice gestito:

    • Il thread corrente (freccia gialla) è stato immesso ServerClass.InstanceMethod. È possibile visualizzare l'ID del thread e lo stack frame di un thread passando il puntatore del mouse su ServerClass.InstanceMethod.
    • Thread 31724 è in attesa di un blocco di proprietà del thread 20272.
    • Il thread principale (lato sinistro) è stato arrestato in [Codice esterno], che è possibile visualizzare in dettaglio se si sceglie Mostra codice esterno.

    Parallel Stacks Window

    In questo esempio, da sinistra a destra vengono visualizzate queste informazioni per il codice gestito:

    • Il thread principale (lato sinistro) è stato arrestato in Thread.Start, dove il punto di arresto è identificato dall'icona dell'indicatore Thread Markerdi thread .
    • Sono stati immessi ServerClass.InstanceMethoddue thread, uno dei quali è il thread corrente (freccia gialla), mentre l'altro thread è stato arrestato in Thread.Sleep.
    • Viene avviato anche un nuovo thread (a destra), ma viene arrestato su ThreadHelper.ThreadStart.
  2. Per visualizzare i thread in una visualizzazione elenco, selezionare Debug>thread di Windows.>

    Screenshot of the Threads Window.

    In questa visualizzazione è possibile vedere facilmente che il thread 20272 è il thread principale e attualmente si trova nel codice esterno, in particolare System.Console.dll.

    Nota

    Per altre informazioni sull'uso della finestra Thread , vedere Procedura dettagliata: Eseguire il debug di un'applicazione multithreading.

  3. Fare clic con il pulsante destro del mouse sulle voci nella finestra Stack paralleli o thread per visualizzare le opzioni disponibili nel menu di scelta rapida.

    È possibile eseguire varie azioni da questi menu di scelta rapida. Per questa esercitazione verranno esaminati altri dettagli nella finestra Espressione di controllo parallelo (sezioni successive).

Impostare un'espressione di controllo su una variabile

  1. Aprire la finestra Espressione di controllo parallelo selezionando Debug>Espressione di controllo>parallelo di Windows>1.

  2. Selezionare la cella in cui viene visualizzato il <Add Watch> testo (o la cella di intestazione vuota nella quarta colonna) e immettere data.

    I valori per la variabile di dati per ogni thread vengono visualizzati nella finestra.

  3. Selezionare la cella in cui viene visualizzato il <Add Watch> testo (o la cella di intestazione vuota nella quinta colonna) e immettere count.

    I valori per la count variabile per ogni thread vengono visualizzati nella finestra. Se non vengono ancora visualizzate molte informazioni, provare a premere F11 alcune volte per avanzare l'esecuzione dei thread nel debugger.

    Parallel Watch Window

  4. Fare clic con il pulsante destro del mouse su una delle righe nella finestra per visualizzare le opzioni disponibili.

Impostare e rimuovere i flag dei thread

È possibile contrassegnare i thread per tenere traccia dei thread importanti e ignorare gli altri thread.

  1. Nella finestra Espressione di controllo parallelo tenere premuto MAIUSCe selezionare più righe.

  2. Fare clic con il pulsante destro del mouse e selezionare Contrassegno.

    Tutti i thread selezionati vengono contrassegnati. È ora possibile filtrare in modo da visualizzare solo i thread contrassegnati.

  3. Nella finestra Espressione di controllo parallelo selezionare il pulsante Show Flagged ThreadsMostra solo thread contrassegnati.

    Nell'elenco vengono visualizzati solo i thread contrassegnati.

    Suggerimento

    Dopo aver contrassegnato alcuni thread, è possibile fare clic con il pulsante destro del mouse su una riga di codice nell'editor di codice e scegliere Esegui thread contrassegnati in cursore. Assicurarsi di scegliere il codice che tutti i thread contrassegnati raggiungeranno. Visual Studio sospende i thread nella riga di codice selezionata, semplificando il controllo dell'ordine di esecuzione bloccando e scongelando i thread.

  4. Selezionare di nuovo il pulsante Mostra solo thread contrassegnati per tornare alla modalità Mostra tutti i thread .

  5. Per rimuovere il flag dei thread, fare clic con il pulsante destro del mouse su uno o più thread contrassegnati nella finestra Espressione di controllo parallelo e selezionare Annulla flag.

Bloccare e sbloccare l'esecuzione del thread

Suggerimento

È possibile bloccare e sbloccare (sospendere e riprendere) thread per controllare l'ordine in cui i thread eseguono il lavoro. Ciò consente di risolvere i problemi di concorrenza, ad esempio deadlock e race condition.

  1. Nella finestra Espressione di controllo parallelo, con tutte le righe selezionate, fare clic con il pulsante destro del mouse e selezionare Blocca.

    Nella seconda colonna viene visualizzata un'icona di sospensione per ogni riga. L'icona di pausa indica che il thread è bloccato.

  2. Deselezionare tutte le altre righe selezionando una sola riga.

  3. Fare clic con il pulsante destro del mouse su una riga e scegliere Thaw.

    L'icona di pausa viene interrotta in questa riga, che indica che il thread non è più bloccato.

  4. Passare all'editor di codice e premere F11. Viene eseguito solo il thread unfrozen.

    L'app potrebbe anche creare un'istanza di alcuni nuovi thread. Tutti i nuovi thread non vengono razziati e non vengono bloccati.

Seguire un singolo thread con punti di interruzione condizionali

Può essere utile seguire l'esecuzione di un singolo thread nel debugger. Un modo per farlo consiste nel bloccare i thread a cui non si è interessati. In alcuni scenari potrebbe essere necessario seguire un singolo thread senza bloccare altri thread, ad esempio per riprodurre un particolare bug. Per seguire un thread senza bloccare altri thread, è necessario evitare l'interruzione del codice tranne nel thread a cui si è interessati. È possibile eseguire questa attività impostando un punto di interruzione condizionale.

È possibile impostare punti di interruzione in condizioni diverse, ad esempio il nome del thread o l'ID del thread. Può essere utile impostare la condizione sui dati noti che è univoca per ogni thread. Questo approccio è comune durante il debug quando si è più interessati a un determinato valore di dati rispetto a qualsiasi thread specifico.

  1. Fare clic con il pulsante destro del mouse sul punto di interruzione creato in precedenza e selezionare Condizioni.

  2. Nella finestra Punto di interruzione Impostazioni immettere data == 5 per l'espressione condizionale.

    Conditional Breakpoint

    Suggerimento

    Se si è più interessati a un thread specifico, usare un nome di thread o un ID thread per la condizione. A tale scopo, nella finestra punto di interruzione Impostazioni selezionare Filtro anziché Espressione condizionale e seguire i suggerimenti per i filtri. È possibile assegnare un nome ai thread nel codice dell'app, perché gli ID thread cambiano quando si riavvia il debugger.

  3. Chiudere la finestra Impostazioni punto di interruzione.

  4. Selezionare il pulsante Riavvia Restart App per riavviare la sessione di debug.

    Si suddivide il codice nel thread in cui il valore della variabile di dati è 5. Nella finestra Espressione di controllo parallelo cercare la freccia gialla che indica il contesto del debugger corrente.

  5. A questo punto, è possibile eseguire il codice (F10) ed eseguire il codice (F11) e seguire l'esecuzione del singolo thread.

    Finché la condizione del punto di interruzione è univoca per il thread e il debugger non raggiunge altri punti di interruzione su altri thread (potrebbe essere necessario disabilitarli), è possibile eseguire il passaggio del codice ed eseguire l'istruzione nel codice senza passare ad altri thread.

    Nota

    Quando si avanza il debugger, verranno eseguiti tutti i thread. Tuttavia, il debugger non suddividerà il codice in altri thread, a meno che uno degli altri thread non raggiunge un punto di interruzione.