Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este tutorial mostra como usar a visualização Tarefas da janela Pilhas paralelas para depurar um aplicativo assíncrono em C#. Esta janela ajuda você a entender e verificar o comportamento em tempo de execução do código que usa o padrão async/await, também chamado de padrão assíncrono baseado em tarefas (TAP).
Para aplicativos que usam a TPL (Biblioteca Paralela de Tarefas), mas não o padrão async/await, ou para aplicativos C++ que usam o Tempo de Execução de Simultaneidade, use a visualização Threads na janela Pilhas Paralelas para depuração. Para obter mais informações, consulte Depurar um deadlock e Exibir threads e tarefas na janela Pilhas paralelas.
A vista Tarefas ajuda-o a:
Visualize visualizações de pilha de chamadas para aplicativos que usam o padrão async/await. Nesses cenários, o modo de exibição Tarefas fornece uma imagem mais completa do estado do seu aplicativo.
Identifique o código assíncrono que está agendado para ser executado, mas ainda não está em execução. Por exemplo, uma solicitação HTTP que não retornou nenhum dado tem mais probabilidade de aparecer no modo de exibição Tarefas em vez do modo de exibição Threads, o que ajuda a isolar o problema.
Ajude a identificar problemas, como o padrão de sync-over-async, bem como dicas sobre possíveis problemas, tais como tarefas bloqueadas ou em espera. O padrão de código sync-over-async refere-se a código que invoca 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 visualizaçã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 deveria 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 virtuais, 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 visualizaçã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 threads, ou "pilhas físicas", porque as pilhas de chamadas assíncronas não estão necessariamente em execução atualmente 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 na visualização Tarefas. Se você estiver bloqueando threads usando métodos como
.Waitou.Result, poderá ver o código na pilha de chamadas físicas.As pilhas de chamadas virtuais assíncronas nem sempre são intuitivas, devido à ramificação que resulta do uso de chamadas de método, como
.WaitAnyou.WaitAll.A janela Pilha de chamadas pode ser útil em combinação com a visualizaçã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. Apenas segmentos idênticos de uma pilha de chamadas virtuais são agrupados. Passe o cursor sobre uma pilha de chamadas agrupadas para identificar os threads que estão executando as tarefas.
Exemplo C#
O código de exemplo neste passo a passo é para um aplicativo que simula um dia na vida de um gorila. O objetivo do exercício é compreender a utilização da visualização de Tarefas na janela de Pilhas Paralelas para depurar um aplicativo assíncrono.
O exemplo inclui uma ilustração do uso do antipadrão sync-over-async, que pode resultar em saturação 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 de início não estiver aberta, escolha Arquivo>Janela de Início.
Na janela Iniciar, escolha Novo projeto.
Na janela Criar um novo projeto , digite ou digite console na caixa de pesquisa. Em seguida, escolha C# na lista Idioma e, em seguida, escolha Windows na lista Plataforma.
Depois de aplicar os filtros de idioma e plataforma, escolha o Aplicativo de Console para .NET e, em seguida, escolha Avançar.
Observação
Se você não vir o modelo correto, vá para Ferramentas>Obter Ferramentas e Recursos..., que abre o Instalador do Visual Studio. Escolha a carga de trabalho de desenvolvimento de desktop .NET e, em seguida, escolha Modificar.
Na janela Configurar seu novo projeto , digite um nome ou use o nome padrão na caixa Nome do projeto . Em seguida, escolha Avançar.
Para .NET, escolha a estrutura de destino recomendada ou .NET 8 e, em seguida, escolha Criar.
Um novo projeto de console é exibido. 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 Compilação, selecione Compilar Solução.
Utilizar a Visã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.A pausa ocorre na segunda chamada para
Gorilla_Start, que acontece dentro de uma segunda tarefa assíncrona.Selecione Depurar > Pilhas Paralelas do > Windows para abrir a janela Pilhas Paralelas e, em seguida, selecione Tarefas na lista suspensa Exibir na janela.
Observe que os rótulos para as 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.
Em contraste com o modo de exibição Tarefas, a janela Pilha de chamadas mostra a pilha de chamadas apenas para o thread atual, não para várias tarefas. Muitas vezes, é útil visualizar ambos juntos para obter uma imagem mais completa do estado do aplicativo.
Sugestão
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 Name da janela Call Stack e depois 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.
Esta vista mostra uma pilha de chamadas assíncronas mais completa após mais métodos terem sido adicionados à cadeia de continuação interna, o que ocorre ao utilizar
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. Exploraremos por que isso acontece nas etapas a seguir.Esta vista também mostra o ícone de bloqueio para
Jungle.Main
. Isso é informativo, mas geralmente não indica um problema. Uma tarefa bloqueada é aquela que é bloqueada porque está aguardando a conclusão de outra tarefa, um evento a ser sinalizado ou um bloqueio a ser liberado.Passe o cursor sobre o
GobbleUpBananasmétodo para obter informações sobre os dois threads que estão executando as tarefas.
A 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 no método
DoSomeMonkeyBusinesspara 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 vê um longo atraso ocorrer, enquanto a vista Tarefas não mostra nenhuma informação da pilha de chamadas assíncrona.
O atraso é causado por uma tarefa de longa duração. Para os fins deste exemplo, simula uma tarefa de longa duração, como uma requisição web, que pode resultar em um caso de saturação do pool de threads. Nada aparece na visualização Tarefas porque, embora as tarefas possam estar bloqueadas, você não está pausado no depurador.
Sugestão
O botão Interromper tudo é uma boa maneira de obter informações da pilha de chamadas se ocorrer um impasse ou se todas as tarefas e threads estiverem atualmente bloqueadas.
Na parte superior do IDE, na barra de ferramentas Depurar, selecione o botão Quebrar tudo (ícone de pausa), Ctrl + Alt + Quebrar.
No topo da pilha de chamadas assíncronas, na vista Tarefas, 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 a localização do problema.No lado esquerdo da captura de tela anterior, a seta verde enrolada indica o contexto atual do depurador. As duas tarefas estã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 dentro da chamada síncrona paraGobbleUpBananas. Este é um exemplo do antipadrão sync-over-async e, se isso ocorresse em um thread da interface do usuário ou em grandes cargas de trabalho de processamento, normalmente seria abordado com uma correção de código usandoawait. Para obter mais informações, consulte Depuração do esgotamento do pool de threads. Para usar ferramentas de análise de desempenho para depurar o esgotamento do pool de threads, consulte Estudo de caso: Isolar um problema de desempenho.Interessantemente,
DoSomeMonkeyBusinessnão aparece na pilha de chamadas. Ele está agendado no momento, não está em execução, portanto, só aparece na pilha de chamadas assíncronas no modo de exibição Tarefas.Sugestão
O depurador quebra em 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á quebrar 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 Quebrar tudo. 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
GobbleUpBananasmétodo pelo código a seguir.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
MorningWalkative GobbleUpBananas usandoawait.await GobbleUpBananas(myResult);Selecione o botão Reiniciar (Ctrl + Shift + F5) e pressione F5 várias vezes até que o aplicativo pareça "travar".
Pressione Quebrar tudo.
Desta vez,
GobbleUpBananasé executado de forma assíncrona. Quando interrompe, vê a pilha de chamadas assíncronas.
A janela Pilha de chamadas está vazia, exceto pela entrada
ExternalCode.O editor de código não nos mostra nada, exceto que fornece uma mensagem indicando que todos os threads estão executando código externo.
No entanto, a vista Tarefas fornece informações úteis.
DoSomeMonkeyBusinessestá na parte superior da pilha de chamadas assíncronas, conforme esperado. Isso nos diz corretamente onde o método de longa duração está localizado. Isso é útil para isolar problemas de async/await quando a pilha de chamadas física na janela Pilha de Chamadas não está a fornecer detalhes suficientes.
Resumo
Neste passo a passo, foi demonstrada a janela do depurador Parallel Stacks. Use esta janela em aplicativos que usam o padrão async/await.