Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este tutorial mostra como usar a exibição Tarefas da janela Pilhas Paralelas para depurar um aplicativo assíncrono em C#. Essa janela ajuda você a entender e verificar o comportamento em tempo de execução do código que usa o padrão assíncrono/await, também chamado de TAP (padrão assíncrono baseado em tarefa).
Para aplicativos que usam a TPL (Biblioteca Paralela de Tarefas), mas não o padrão assíncrono/await, ou para aplicativos C++ usando o Runtime de Simultaneidade, use a exibição Threads na janela Pilhas Paralelas para depuração. Para obter mais informações, consulte Depurar um deadlock e visualizar threads e tarefas na janela Pilhas Paralelas.
A exibição Tarefas ajuda você a:
Exibir visualizações da pilha de chamadas para aplicativos que utilizam o padrão async/await. Nesses cenários, o modo de exibição Tarefas fornece uma imagem mais completa do estado do aplicativo.
Identifique o código assíncrono que está programado para ser executado, mas ainda não está em execução. Por exemplo, uma solicitação HTTP que não retornou dados é mais provável que apareça no modo de exibição Tarefas em vez do modo de exibição Threads, o que ajuda você a isolar o problema.
Ajude a identificar questões como o padrão de sincronização-sobre-assíncrono, juntamente com dicas relacionadas a questões potenciais, como tarefas bloqueadas ou de espera. O padrão de código sync-over-async refere-se ao código que chama métodos assíncronos de maneira síncrona, o que é conhecido por bloquear threads e é a causa mais comum de esgotamento do pool de threads.
Pilhas de chamadas assíncronas
A exibição Tarefas em Pilhas Paralelas fornece uma visualização para pilhas de chamadas assíncronas, para que você possa ver o que está acontecendo (ou deve acontecer) em seu aplicativo.
Aqui estão alguns pontos importantes a serem lembrados ao interpretar dados no modo de exibição Tarefas.
Pilhas de chamadas assíncronas são pilhas de chamadas lógicas ou pilhas virtuais, e não pilhas de chamadas físicas que representam a pilha. Ao trabalhar com código assíncrono (por exemplo, usando a
awaitpalavra-chave), o depurador fornece uma exibição das "pilhas de chamadas assíncronas" ou "pilhas de chamadas virtuais". As pilhas de chamadas assíncronas são diferentes das pilhas de chamadas baseadas em thread ou das "pilhas físicas", porque as pilhas de chamadas assíncronas não estão necessariamente em execução no momento em nenhum thread físico. Em vez disso, as pilhas de chamadas assíncronas são continuações ou "promessas" de código que serão executadas no futuro, de forma assíncrona. As pilhas de chamadas são criadas usando continuações.O código assíncrono agendado, mas não em execução no momento, não aparece na pilha de chamadas físicas, mas deve aparecer na pilha de chamadas assíncronas no modo de exibição Tarefas. Se você estiver bloqueando threads usando métodos como
.Waitou.Result, poderá ver o código na pilha de chamadas física.Empilhamentos de chamadas virtuais assíncronas nem sempre são intuitivos, devido à ramificação resultante do uso de chamadas de método como
.WaitAnyou.WaitAll.A janela Pilha de Chamadas pode ser útil em combinação com o modo de exibição Tarefas, pois mostra a pilha de chamadas físicas para o thread em execução atual.
Seções idênticas da pilha de chamadas virtuais são agrupadas para simplificar a visualização de aplicativos complexos.
A animação conceitual a seguir mostra como o agrupamento é aplicado a pilhas de chamadas virtuais. Somente segmentos idênticos de uma pilha de chamadas virtuais são agrupados. Passe o mouse sobre uma pilha de chamadas agrupada para idenitfy os threads que estão executando as tarefas.
Exemplo de C#
O código de exemplo neste passo a passo é para um aplicativo que simula um dia na vida de um gorila. A finalidade do exercício é entender como usar a visualização de Tarefas na janela de Pilhas Paralelas para depurar um aplicativo assíncrono.
O exemplo inclui o uso do antipadrão de sincronização sobre assíncrono, o que pode resultar no esgotamento do pool de threads.
Para tornar a pilha de chamadas intuitiva, o aplicativo de exemplo executa as seguintes etapas sequenciais:
- Cria um objeto que representa um gorila.
- Gorila acorda.
- Gorila vai em uma caminhada matinal.
- Gorila encontra bananas na selva.
- Gorila come.
- Gorila se envolve em negócios de macacos.
Criar o projeto de exemplo
Abra o Visual Studio e crie um novo projeto.
Se a janela inicial não estiver aberta, escolha Arquivo>Janela de Início.
Na janela inicial, escolha Novo projeto.
Na janela Criar um novo projeto , insira ou digite o console na caixa de pesquisa. Em seguida, escolha C# na lista De idiomas e escolha o Windows na lista plataforma.
Depois de aplicar os filtros de linguagem e plataforma, escolha o Aplicativo de Console para .NET e escolha Avançar.
Observação
Se você não vir o modelo correto, vá para Ferramentas>Obter Ferramentas e Recursos..., o que abre o Instalador do Visual Studio. Escolha a carga de trabalho Desenvolvimento de área de trabalho do .NET e, em seguida, selecione Modificar.
Na janela Configurar seu novo projeto, digite um nome ou use o nome padrão na caixa de nome do Projeto . Em seguida, escolha Avançar.
Para .NET, escolha a estrutura de destino recomendada ou o .NET 8 e, em seguida, escolha Criar.
Um novo projeto de console aparece. Depois que o projeto for criado, um arquivo de origem será exibido.
Abra o arquivo de código .cs no projeto. Exclua seu conteúdo para criar um arquivo de código vazio.
Cole o código a seguir para o idioma escolhido no arquivo de código vazio.
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"); } } }Depois de atualizar o arquivo de código, salve as alterações e crie a solução.
No menu Arquivo, selecione Salvar Tudo.
No menu Build, selecione Compilar Solução.
Usar a Exibição de Tarefas da janela Pilhas Paralelas
No menu Depurar , selecione Iniciar Depuração (ou F5) e aguarde até que o primeiro
Debugger.Break()seja atingido.Pressione F5 uma vez e o depurador pausa novamente na mesma
Debugger.Break()linha.Isso é pausado na segunda chamada para
Gorilla_Start, que ocorre dentro de uma segunda tarefa assíncrona.Selecione Debug > Windows > Pilhas Paralelas para abrir a janela Pilhas Paralelas e selecione Tarefas no menu suspenso Exibir na janela.
Observe que as etiquetas das pilhas de chamadas assíncronas descrevem 2 Pilhas Lógicas Assíncronas. Quando você pressionou F5 pela última vez, você iniciou outra tarefa. Para simplificação em aplicativos complexos, pilhas de chamadas assíncronas idênticas são agrupadas em uma única representação visual. Isso fornece informações mais completas, especialmente em cenários com muitas tarefas.
Ao contrário do modo de exibição Tarefas, a janela Pilha de Chamadas mostra a pilha de chamadas somente para o thread atual, não para várias tarefas. Geralmente, é útil exibir ambos juntos para uma imagem mais completa do estado do aplicativo.
Dica
A janela Pilha de Chamadas pode mostrar informações, como um deadlock, usando a descrição
Async cycle.Durante a depuração, você pode alternar se o código externo é exibido. Para alternar o recurso, clique com o botão direito do mouse no cabeçalho da tabela Nome da janela Pilha de Chamadas e selecione ou desmarque Mostrar Código Externo. Se você mostrar código externo, ainda poderá usar este passo a passo, mas seus resultados podem ser diferentes das ilustrações.
Pressione F5 novamente e o depurador pausa no
DoSomeMonkeyBusinessmétodo.
Essa exibição mostra uma pilha de chamadas assíncrona mais completa depois que métodos mais assíncronos foram adicionados à cadeia de continuação interna, que ocorre ao usar
awaite métodos semelhantes.DoSomeMonkeyBusinesspode ou não estar presente na parte superior da pilha de chamadas assíncronas porque é um método assíncrono, mas ainda não foi adicionado à cadeia de continuação. Vamos explorar por que esse é o caso nas etapas a seguir.Essa exibição também mostra o ícone bloqueado para
Jungle.Main
. Isso é informativo, mas geralmente não indica um problema. Uma tarefa bloqueada é aquela bloqueada porque está aguardando a conclusão de outra tarefa, um evento a ser sinalizado ou um bloqueio a ser liberado.Passe o mouse sobre o
GobbleUpBananasmétodo para obter informações sobre os dois threads que estão executando as tarefas.
O thread atual também aparece na lista Thread na barra de ferramentas de depuração.
Você pode usar a lista Thread para alternar o contexto do depurador para um thread diferente.
Pressione F5 novamente e o depurador pausa o
DoSomeMonkeyBusinessmétodo para a segunda tarefa.
Dependendo do tempo de execução da tarefa, neste ponto você verá pilhas de chamadas assíncronas separadas ou agrupadas.
Na ilustração anterior, as pilhas de chamadas assíncronas para as duas tarefas são separadas porque não são idênticas.
Pressione F5 novamente, e você verá um longo atraso e a visualização de Tarefas não mostra nenhuma informação sobre o stack de chamadas assíncronas.
O atraso é causado por uma tarefa de execução prolongada. Para fins deste exemplo, ele simula uma tarefa de execução longa, como uma solicitação da web, o que pode resultar em um caso de esgotamento do pool de threads. Nada aparece na visão 'Tarefas' porque, embora as tarefas estejam bloqueadas, você não está em pausa no depurador no momento.
Dica
O botão Interromper Tudo é uma boa maneira de obter informações de pilha de chamadas se ocorrer um deadlock ou todas as tarefas e threads estiverem bloqueados no momento.
Na parte superior do IDE na barra de ferramentas Depurar, selecione o botão Interromper Tudo (ícone de pausa), Ctrl + Alt + Quebra.
Próximo ao topo da pilha de chamadas assíncronas na visualização de Tarefas, você vê que
GobbleUpBananasestá bloqueado. Na verdade, duas tarefas são bloqueadas no mesmo ponto. Uma tarefa bloqueada não é necessariamente inesperada e não significa necessariamente que há um problema. No entanto, o atraso observado na execução indica um problema e as informações da pilha de chamadas aqui mostram o local do problema.No lado esquerdo da captura de tela anterior, a seta verde ondulada indica o contexto atual do depurador. As duas tarefas são bloqueadas em
mb.Wait()no métodoGobbleUpBananas.A janela Pilha de Chamadas também mostra que o thread atual está bloqueado.
A chamada para
Wait()bloqueia os threads na chamada síncrona paraGobbleUpBananas. Este é um exemplo do antipadrão sincronizado sobre assíncrono e, se isso ocorresse em um thread de interface do usuário ou em cargas de trabalho de processamento grandes, ele normalmente seria resolvido com uma correção de código usandoawait. Para obter mais informações, consulte Depuração da exaustão do pool de threads. Para usar ferramentas de profiling para depurar o esgotamento do pool de threads, consulte Estudo de caso: Isolar um problema de desempenho.É interessante notar que,
DoSomeMonkeyBusinessnão aparece na pilha de chamadas. No momento, ele está agendado, não está em execução, portanto, ele só aparece na pilha de chamadas assíncronas no modo de exibição Tarefas.Dica
O depurador divide o código por thread. Por exemplo, isso significa que, se você pressionar F5 para continuar a execução e o aplicativo atingir o próximo ponto de interrupção, ele poderá dividir o código em um thread diferente. Se você precisar gerenciar isso para fins de depuração, poderá adicionar pontos de interrupção adicionais, adicionar pontos de interrupção condicionais ou usar Break All. Para obter mais informações sobre esse comportamento, consulte Seguir um único thread com pontos de interrupção condicionais.
Corrigir o código de exemplo
Substitua o método
GobbleUpBananaspelo seguinte código.public async Task GobbleUpBananas(int food) // Previously returned void. { Console.WriteLine("Trying to gobble up food..."); //Task mb = DoSomeMonkeyBusiness(); //mb.Wait(); await DoSomeMonkeyBusiness(); }No método
MorningWalk, chame GobbleUpBananas usandoawait.await GobbleUpBananas(myResult);Selecione o botão Reiniciar (Ctrl + Shift + F5) e pressione F5 várias vezes até que o aplicativo apareça como "trava".
Pressione Break All.
Desta vez,
GobbleUpBananasé executado de forma assíncrona. Quando você interrompe, você vê a pilha de chamadas assíncronas.
A janela Pilha de Chamadas está vazia, exceto pela
ExternalCodeentrada.O editor de código não nos mostra nada, exceto que ele fornece uma mensagem indicando que todos os threads estão executando código externo.
No entanto, a visualização de tarefas fornece informações úteis.
DoSomeMonkeyBusinessestá na parte superior da pilha de chamadas assíncrona, conforme o esperado. Isso nos informa corretamente onde o método de longa execução está localizado. Isso é útil para isolar problemas de async/await quando a pilha de chamadas física na janela de Pilha de Chamadas não estiver fornecendo detalhes suficientes.
Resumo
Este passo a passo demonstrou a janela do depurador Pilhas Paralelas. Use esta janela em aplicativos que utilizam o padrão async/await.