Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questa esercitazione illustra come usare la visualizzazione attività della finestra Stack paralleli per eseguire il debug di un'applicazione asincrona C#. Questa finestra consente di comprendere e verificare il comportamento di runtime del codice che usa il modello asincrono/await, detto anche modello asincrono basato su attività (TAP).
Per le app che usano la libreria TPL (Task Parallel Library), ma non il modello async/await o per le app C++ che usano il runtime di concorrenza, usare la visualizzazione Thread nella finestra Stack paralleli per il debug. Per altre informazioni, vedere Eseguire il debug di un deadlock e visualizzare thread e attività nella finestra Stack paralleli.
La visualizzazione Attività consente di:
Visualizzare le visualizzazioni dello stack di chiamate per le app che usano il modello async/await. In questi scenari, la visualizzazione Attività offre un quadro più completo dello stato dell'app.
Identificare il codice asincrono pianificato per l'esecuzione ma che non è ancora in esecuzione. Ad esempio, una richiesta HTTP che non ha restituito dati è più probabile che venga visualizzata nella visualizzazione Attività anziché nella visualizzazione Thread, che consente di isolare il problema.
Aiuta a identificare problemi come il modello di sincronizzazione su asincrono, insieme alle indicazioni relative a potenziali problemi, come attività bloccate o in attesa. Il modello di codice sync-over-async si riferisce al codice che chiama metodi asincroni in modo sincrono, che è noto per bloccare i thread ed è la causa più comune della fame del pool di thread.
Stack di chiamate asincrone
La vista delle Attività in Stack Paralleli fornisce una rappresentazione degli stack di chiamate asincrone, in modo da poter vedere cosa accade (o dovrebbe accadere) nella tua applicazione.
Ecco alcuni punti importanti da ricordare durante l'interpretazione dei dati nella visualizzazione Attività.
Gli stack di chiamate asincroni sono stack di chiamate logiche o virtuali, non stack di chiamate fisiche che rappresentano lo stack. Quando si usa il codice asincrono ,ad esempio usando la
awaitparola chiave , il debugger fornisce una visualizzazione degli "stack di chiamate asincroni" o "stack di chiamate virtuali". Gli stack di chiamate asincroni sono diversi dagli stack di chiamate basati su thread o "stack fisici", perché gli stack di chiamate asincroni non sono necessariamente in esecuzione in alcun thread fisico. Gli stack di chiamate asincroni sono invece continuazioni o "promesse" di codice che verranno eseguiti in futuro, in modo asincrono. Gli stack di chiamate vengono creati usando le continuazioni.Il codice asincrono pianificato ma non attualmente in esecuzione non viene visualizzato nello stack di chiamate fisiche, ma dovrebbe essere visualizzato nello stack di chiamate asincrono nella visualizzazione Attività. Se si bloccano i thread usando metodi come
.Waito.Result, è possibile che il codice venga visualizzato nello stack di chiamate fisiche.Gli stack di chiamate virtuali asincroni non sono sempre intuitivi, a causa della diramazione risultante dall'uso di chiamate di metodo come
.WaitAnyo.WaitAll.La finestra Stack di chiamate può essere utile in combinazione con la visualizzazione Attività, perché mostra lo stack di chiamate fisiche per il thread in esecuzione corrente.
Le sezioni identiche dello stack di chiamate virtuali vengono raggruppate per semplificare la visualizzazione per app complesse.
L'animazione concettuale seguente illustra come il raggruppamento viene applicato agli stack di chiamate virtuali. Vengono raggruppati solo segmenti identici di uno stack di chiamate virtuali. Passare il puntatore del mouse su uno stack di chiamate raggruppate per indefy i thread che eseguono le attività.
Esempio in C#
Il codice di esempio in questa procedura dettagliata è per un'applicazione che simula un giorno nella vita di un gorilla. Lo scopo dell'esercizio è comprendere come usare la visualizzazione delle attività della finestra Stack paralleli per eseguire il debug di un'applicazione asincrona.
L'esempio include un esempio dell'utilizzo dell'antipattern sincrono-su-asincrono, che può comportare l'esaurimento del pool di thread.
Per rendere intuitivo lo stack di chiamate, l'app di esempio esegue i passaggi sequenziali seguenti:
- Crea un oggetto che rappresenta un gorilla.
- Gorilla si sveglia.
- Gorilla va in una passeggiata di mattina.
- Gorilla trova banane nella giungla.
- Gorilla mangia.
- Gorilla si impegna in attività da furfante.
Creare il progetto di esempio
Aprire Visual Studio e creare un nuovo progetto.
Se la finestra iniziale non è aperta, scegliere Finestradi avvio>.
Nella finestra iniziale scegliere Nuovo progetto.
Nella finestra Crea un nuovo progetto immettere o digitare console nella casella di ricerca. Scegliere quindi C# dall'elenco Linguaggio e quindi scegliere Windows dall'elenco Piattaforma.
Dopo aver applicato i filtri di linguaggio e piattaforma, scegliere l'app console per .NET e quindi scegliere Avanti.
Annotazioni
Se non vedi il modello corretto, vai a Strumenti>Recupera strumenti e funzionalità... Verrà aperto il programma di installazione di Visual Studio. Scegliere il carico di lavoro Sviluppo di applicazioni desktop .NET, quindi scegliere Modifica.
Nella finestra Configura il nuovo progetto digitare un nome o usare il nome predefinito nella casella Nome progetto . Scegliere quindi Avanti.
Per .NET scegliere il framework di destinazione consigliato o .NET 8 e quindi scegliere Crea.
Appare un nuovo progetto console. Dopo aver creato il progetto, viene visualizzato un file di origine.
Aprire il file di codice .cs nel progetto. Eliminare il contenuto per creare un file di codice vuoto.
Incollare il codice seguente per la lingua scelta nel file di codice vuoto.
using System.Diagnostics; namespace AsyncTasks_SyncOverAsync { class Jungle { public static async Task<int> FindBananas() { await Task.Delay(1000); Console.WriteLine("Got bananas."); return 0; } static async Task Gorilla_Start() { Debugger.Break(); Gorilla koko = new Gorilla(); int result = await Task.Run(koko.WakeUp); } static async Task Main(string[] args) { List<Task> tasks = new List<Task>(); for (int i = 0; i < 2; i++) { Task task = Gorilla_Start(); tasks.Add(task); } await Task.WhenAll(tasks); } } class Gorilla { public async Task<int> WakeUp() { int myResult = await MorningWalk(); return myResult; } public async Task<int> MorningWalk() { int myResult = await Jungle.FindBananas(); GobbleUpBananas(myResult); return myResult; } /// <summary> /// Calls a .Wait. /// </summary> public void GobbleUpBananas(int food) { Console.WriteLine("Trying to gobble up food synchronously..."); Task mb = DoSomeMonkeyBusiness(); mb.Wait(); } public async Task DoSomeMonkeyBusiness() { Debugger.Break(); while (!System.Diagnostics.Debugger.IsAttached) { Thread.Sleep(100); } await Task.Delay(30000); Console.WriteLine("Monkey business done"); } } }Dopo aver aggiornato il file di codice, salvare le modifiche e compilare la soluzione.
Scegliere Salva tutto dal menu File.
Scegliere Compila soluzione dal menu Compila.
Usare la visualizzazione delle attività della finestra dello Stack parallelo
Nel menu Debug selezionare Avvia debug (o F5) e attendere che venga raggiunto il primo
Debugger.Break().Premere F5 una volta e il debugger si sospende nuovamente sulla stessa
Debugger.Break()riga.Questa operazione viene sospesa nella seconda chiamata a
Gorilla_Start, che si verifica all'interno di una seconda attività asincrona.Selezionare Debug > stack paralleli di Windows > per aprire la finestra Stack paralleli e quindi selezionare Attività dall'elenco a discesa Visualizza nella finestra.
Si noti che le etichette per gli stack di chiamate asincrone descrivono 2 stack logici asincroni. Quando si preme F5 per l'ultima volta, è stata avviata un'altra attività. Per semplificare le app complesse, gli stack di chiamate asincroni identici vengono raggruppati in una singola rappresentazione visiva. In questo modo vengono fornite informazioni più complete, in particolare negli scenari con molte attività.
A differenza della visualizzazione Attività, nella finestra Stack di chiamate viene visualizzato lo stack di chiamate solo per il thread corrente, non per più attività. Spesso è utile visualizzarli entrambi insieme per un quadro più completo dello stato dell'app.
Suggerimento
La finestra Call Stack può visualizzare informazioni tra cui un deadlock, usando la descrizione
Async cycle.Durante il debug, è possibile attivare o disattivare la visualizzazione di codice esterno. Per attivare o disattivare la funzionalità, fare clic con il pulsante destro del mouse sull'intestazione della tabella Nome della finestra Stack di chiamate e quindi selezionare o deselezionare Mostra codice esterno. Se si visualizza codice esterno, è comunque possibile usare questa procedura dettagliata, ma i risultati potrebbero differire dalle illustrazioni.
Premere di nuovo F5 e il debugger viene sospeso nel metodo
DoSomeMonkeyBusiness.
Questa vista mostra uno stack di chiamate asincrono più completo dopo l'aggiunta di altri metodi asincroni alla catena di continuazione interna, che si verifica quando si usano
awaite metodi simili.DoSomeMonkeyBusinesspuò o non essere presente all'inizio dello stack di chiamate asincrone perché è un metodo asincrono ma non è ancora stato aggiunto alla catena di continuazione. Si esaminerà il motivo per cui questo è il caso nei passaggi che seguono.Questa visualizzazione mostra anche l'icona bloccata per
Jungle.Main
. Questo è informativo, ma in genere non indica un problema. Un'attività bloccata è un'attività bloccata perché è in attesa del completamento di un'altra attività, un evento da segnalare o un blocco da rilasciare.Passare il puntatore del mouse sul
GobbleUpBananasmetodo per ottenere informazioni sui due thread che eseguono le attività.
Il thread corrente viene visualizzato anche nell'elenco Thread sulla barra degli strumenti Debug.
È possibile usare l'elenco Thread per cambiare il contesto del debugger in un thread diverso.
Premere di nuovo F5 e il debugger si ferma nel metodo
DoSomeMonkeyBusinessper la seconda attività.
A seconda dell'intervallo di esecuzione dell'attività, a questo punto vengono visualizzati stack di chiamate asincroni separati o raggruppati.
Nella figura precedente gli stack di chiamate asincroni per le due attività sono separati perché non sono identici.
Premere di nuovo F5 e si noterà un lungo ritardo e la visualizzazione Attività non mostra informazioni sullo stack di chiamate asincrone.
Il ritardo è causato da un'attività a esecuzione prolungata. Ai fini di questo esempio, simula un'attività a esecuzione prolungata, come una richiesta web, che potrebbe causare l'esaurimento del pool di thread. Non viene visualizzato alcun elemento nella visualizzazione Attività perché anche se le attività potrebbero essere bloccate non sono attualmente sospese nel debugger.
Suggerimento
Il pulsante Interrompi tutto è un buon modo per ottenere informazioni sullo stack di chiamate se si verifica un deadlock o tutte le attività e i thread sono attualmente bloccati.
Nella parte superiore dell'IDE nella barra degli strumenti Debug selezionare il pulsante Interrompi tutto (icona pausa), CTRL + ALT + INTERR.
Nella parte superiore dello stack di chiamate asincrone nella visualizzazione Attività si noterà che
GobbleUpBananasè bloccato. Infatti, due attività vengono bloccate allo stesso punto. Un'attività bloccata non è necessariamente imprevista e non significa necessariamente che si verifichi un problema. Tuttavia, il ritardo osservato nell'esecuzione indica un problema e le informazioni sullo stack di chiamate qui mostrano la posizione del problema.Nel lato sinistro dello screenshot precedente, la freccia verde arricciata indica il contesto del debugger corrente. Le due attività vengono bloccate nel metodo
mb.Wait()suGobbleUpBananas.La finestra Stack di chiamate mostra anche che il thread corrente è bloccato.
La chiamata a
Wait()blocca i thread all'interno della chiamata sincrona aGobbleUpBananas. Questo è un esempio dell'antipattern di sincronizzazione sopra asincronia e, se questo avviene su un thread UI o sotto carichi di lavoro elevati di elaborazione, in genere viene risolto con una correzione nel codice usandoawait. Per altre informazioni, vedere Debug dell'esaurimento del pool di thread. Per utilizzare gli strumenti di profilatura per il debug dell'esaurimento del pool di thread, consultare Studio di caso: Isolare un problema di prestazioni.Inoltre,
DoSomeMonkeyBusinessnon viene visualizzato nello stack di chiamate. È attualmente pianificato, non in esecuzione, quindi viene visualizzato solo nello stack di chiamate asincrone nella vista Attività.Suggerimento
Il debugger suddivide il codice in base al thread. Questo significa, ad esempio, che se si preme F5 per continuare l'esecuzione e l'app raggiunge il punto di interruzione successivo, potrebbe entrare nel codice in un thread diverso. Se è necessario gestirlo a scopo di debug, è possibile aggiungere punti di interruzione aggiuntivi, aggiungere punti di interruzione condizionali o usare Interrompi tutto. Per altre informazioni su questo comportamento, vedere Seguire un singolo thread con punti di interruzione condizionali.
Correggere il codice di esempio
Sostituire il
GobbleUpBananasmetodo con il codice seguente.public async Task GobbleUpBananas(int food) // Previously returned void. { Console.WriteLine("Trying to gobble up food..."); //Task mb = DoSomeMonkeyBusiness(); //mb.Wait(); await DoSomeMonkeyBusiness(); }Nel metodo
MorningWalk, chiamare GobbleUpBananas utilizzandoawait.await GobbleUpBananas(myResult);Selezionare il pulsante Riavvia (CTRL+ MAIUSC + F5) e quindi premere F5 più volte finché l'app non sembra bloccarsi.
Premere Interrompi tutto.
Questa volta,
GobbleUpBananasviene eseguito in modo asincrono. Quando si interrompe, viene visualizzato lo stack di chiamate asincrono.
La finestra del call stack è vuota, ad eccezione della voce
ExternalCode.L'editor di codice non mostra nulla, ma fornisce un messaggio che indica che tutti i thread eseguono codice esterno.
Tuttavia, la visualizzazione Attività fornisce informazioni utili.
DoSomeMonkeyBusinesssi trova in cima allo stack di chiamate asincrone, come previsto. Questo indica correttamente dove si trova il metodo a esecuzione prolungata. Ciò è utile per isolare le problematiche async/await quando lo stack delle chiamate fisico nella finestra dello stack delle chiamate non fornisce dettagli sufficienti.
Riassunto
Questa procedura dettagliata ha illustrato la finestra del debugger Parallel Stacks . Usare questa finestra nelle app che usano il modello async/await.