Introdução à depuração de aplicativos multithreaded (C#, Visual Basic, C++)

O Visual Studio fornece várias ferramentas e elementos de interface do usuário para ajudar você a depurar aplicativos multithreaded. Este tutorial mostra como usar marcadores de thread, a janela Pilhas Paralelas, a janela Inspeção Paralela, pontos de interrupção condicionais e pontos de interrupção de filtro. A conclusão deste tutorial familiariza você com os recursos do Visual Studio para depuração de aplicativos multithread.

Esses dois artigos fornecem informações adicionais sobre o uso de outras ferramentas de depuração multithread:

A primeira etapa é criar um projeto de aplicativo multithread.

Criar um projeto de aplicativo multithreaded

  1. Abra o Visual Studio e crie um projeto.

    Se a janela inicial não estiver aberta, escolha Arquivo>Janela Inicial.

    Na tela Iniciar, selecione Criar um novo projeto.

    Na janela Criar um novo projeto, insira ou digite console na caixa de pesquisa. Em seguida, escolha C#, C++ ou Visual Basic na lista Linguagem de programação e, em seguida, escolha Windows na lista Plataforma.

    Depois de aplicar os filtros de linguagem de programação e plataforma, escolha o Aplicativo de Console para .NET ou C++ e, em seguida, clique em Avançar.

    Observação

    Caso não veja o modelo correto, vá em Ferramentas>Obter Ferramentas e Recursos..., que abre o Instalador do Visual Studio. Escolha a carga de trabalho Desenvolvimento para desktop com .NET ou Desenvolvimento para desktop com C++ e, em seguida, selecione Modificar.

    Na janela Configurar seu novo projeto, digite ou insira MyThreadWalkthroughApp na caixa Nome do projeto. Em seguida, escolha Avançar ou Criar, qualquer dessas opções que estiver disponível.

    Para um projeto .NET Core ou .NET 5+, escolha a estrutura de direcionamento recomendada ou o .NET 8 e, em seguida, escolha Criar.

    Um novo projeto de console é exibido. Quando o projeto tiver sido criado, um arquivo de origem aparecerá. Dependendo da linguagem escolhida, o arquivo de origem poderá ser chamado de Program.cs, MyThreadWalkthroughApp.cpp ou Module1.vb.

  2. Exclua o código que aparece no arquivo de origem e substitua-o pelo código atualizado a seguir. Escolha o snippet apropriado para sua configuração de código.

    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. No menu Arquivo, selecione Salvar Tudo.

  4. (Somente Visual Basic) No Gerenciador de Soluções (painel direito), clique com o botão direito do mouse no nó do projeto e escolha Propriedades. Na guia Aplicativo, altere o Objeto de inicialização para Simples.

Depurar o aplicativo multithreaded

  1. No editor de código-fonte, procure o seguinte snippet de código:

    Thread.Sleep(3000);
    Console.WriteLine();
    
  2. Clique com o botão esquerdo na medianiz esquerda da instrução Thread.Sleep ou, para C++, std::this_thread::sleep_for, para inserir um novo ponto de interrupção.

    Na sarjeta, um círculo vermelho indica que um ponto de interrupção está definido neste local.

  3. No menu Depurar, selecione Iniciar Depuração (F5).

    O Visual Studio cria a solução, o aplicativo começa a ser executado com o depurador anexado e, em seguida, o aplicativo para no ponto de interrupção.

  4. No editor de código-fonte, localize a linha que contém o ponto de interrupção.

Descobrir o marcador de thread

  1. Na Barra de Ferramentas de Depuração, selecione o botão Mostrar Threads na OrigemMostrar Threads na Origem.

  2. Pressione F11 duas vezes para avançar o depurador.

  3. Examine a medianiz no lado esquerdo da janela. Nessa linha, observe um ícone marcador de thread Marcador de Thread que se assemelha a dois threads retorcidos. O marcador de thread indica que um thread está parado nesse local.

    Um marcador de threads pode ser parcialmente ocultado por um ponto de interrupção.

  4. Passe o ponteiro sobre o marcador de thread. O DataTip aparece, mostrando o nome e o número de ID do thread para cada thread interrompido. Nesse caso, o nome provavelmente é <noname>.

    Captura de tela da ID do Thread em um DataTip.

  5. Selecione o marcador de thread para ver as opções disponíveis no menu de atalho.

Exibir os locais de thread

Na janela Pilhas Paralelas, você pode alternar entre uma exibição Threads e a exibição Tarefas (para programação baseada em tarefas) e exibir informações de pilha de chamadas para cada thread. Neste aplicativo, podemos usar a exibição Threads.

  1. Abra a janela Pilhas Paralelas escolhendo Depurar>Janelas>Pilhas Paralelas. Você deverá ver algo semelhante ao mostrado a seguir. As informações exatas podem ser diferentes dependendo da localização atual de cada thread, do hardware e da linguagem de programação.

    Captura de tela da janela Parallel Stacks.

    Neste exemplo, da esquerda para a direita, vemos estas informações para o código gerenciado:

    • O thread atual (seta amarela) inseriu ServerClass.InstanceMethod. Você pode exibir a ID do thread e o quadro de pilha de um thread passando o mouse sobre ServerClass.InstanceMethod.
    • O thread 31724 está aguardando um bloqueio pertencente ao thread 20272.
    • O thread principal (lado esquerdo) parou em [código externo], que você pode exibir em detalhes se escolher Mostrar código externo.

    Janela de Pilhas Paralelas

    Neste exemplo, da esquerda para a direita, vemos estas informações para o código gerenciado:

    • O principal thread (lado esquerdo) parou em Thread.Start, em que o ponto de parada é identificado pelo ícone do marcador de thread Thread Marker.
    • Dois threads entraram no ServerClass.InstanceMethod, um dos quais é o thread atual (seta amarela), enquanto o outro thread parou em Thread.Sleep.
    • Um novo thread (à direita) também está sendo iniciado, mas é interrompido em ThreadHelper.ThreadStart.
  2. Para exibir os threads em uma exibição de lista, selecione Depurar>Windows>Threads.

    Captura de tela da janela Threads.

    Nesse modo de exibição, você pode facilmente ver que o thread 20272 é o thread principal e está localizado atualmente em código externo, especificamente System.Console.dll.

    Observação

    Para obter mais informações sobre como usar a janela Threads, confira Passo a passo: depurar um aplicativo multithread.

  3. Clique com o botão direito do mouse em entradas na janela Pilhas Paralelas ou Threads para ver as opções disponíveis no menu de atalho.

    Você pode realizar várias ações a partir desses menus de clique com o botão direito do mouse. Neste tutorial, explore mais desses detalhes na janela Observação Paralela (próximas seções).

Definir uma inspeção em uma variável

  1. Abra a janela Inspeção Paralela selecionando Depurar>Janela>Inspeção Paralela>Inspeção Paralela 1.

  2. Selecione a célula em que você vê o texto <Add Watch> (ou a célula de cabeçalho vazia na quarta coluna) e insira data.

    Os valores da variável de dados para cada thread aparecem na janela.

  3. Selecione a célula em que você vê o texto <Add Watch> (ou a célula de cabeçalho vazia na quinta coluna) e insira count.

    Os valores da variável count para cada thread aparecem na janela. Se você ainda não vir essas informações, tente pressionar F11 algumas vezes para avançar a execução dos threads no depurador.

    Janela de Inspeção Paralela

  4. Clique com o botão direito do mouse em uma das linhas da janela para ver as opções disponíveis.

Sinalizar e remover sinalizador de threads

Você pode sinalizar threads para controlar threads importantes e ignorar os outros threads.

  1. Na janela Inspeção Paralela, mantenha a tecla Shift pressionada e selecione várias linhas.

  2. Clique com o botão direito do mouse e selecione Sinalizador.

    Todos os threads selecionados são sinalizados. Agora, você pode filtrar para mostrar apenas threads sinalizados.

  3. Na janela Inspeção Paralela, selecione o botão Mostrar Somente Threads SinalizadosMostrar Threads Sinalizados.

    Somente os threads sinalizados aparecem na lista.

    Dica

    Depois de sinalizar alguns threads, você pode clicar com o botão direito do mouse em uma linha de código no editor de código e escolha Executar Threads Sinalizados no Cursor. Certifique-se de escolher um código que seja alcançado por todos os threads sinalizados. O Visual Studio pausará threads na linha de código selecionada, facilitando o controle da ordem de execução congelando e descongelando threads.

  4. Selecione o botão Mostrar Somente Threads Sinalizados novamente para alternar novamente para o modo Mostrar Todos os Threads.

  5. Para remover a sinalização de threads, clique com o botão direito do mouse em um ou mais threads sinalizados na janela Inspeção Paralela e selecione Remover sinalizador.

Congelar e descongelar a execução do thread

Dica

Você pode congelar e descongelar threads (suspender e retomar) para controlar a ordem na qual os threads executam o trabalho. Isso pode ajudar você a resolver problemas de simultaneidade, como deadlocks e condições de corrida.

  1. Na janela Inspeção Paralela, com todas as linhas selecionadas, clique com o botão direito do mouse e selecione Congelar.

    Na segunda coluna, um ícone de pausa é exibido para cada linha. O ícone de pausa indica que o thread está congelado.

  2. Desmarque todas as outras linhas selecionando apenas uma linha.

  3. Clique com o botão direito do mouse em uma linha e selecione Descongelar.

    O ícone de pausa desaparece nessa linha, o que indica que os threads não estão mais congelados.

  4. Alterne para o editor de código e pressione F11. Somente o thread não descongelado é executado.

    O aplicativo também pode criar uma instância para alguns threads novos. Os novos threads não são marcados e não são congelados.

Seguir um thread com pontos de interrupção condicionais

Pode ser útil seguir a execução de apenas um thread no depurador. Uma maneira de fazer isso é congelar os threads nos quais você não está interessado. Em alguns cenários, pode ser necessário seguir um único thread sem congelar outros threads, por exemplo, para reproduzir um bug específico. Para seguir um thread sem congelar outros threads, você deve evitar interromper o código, exceto no thread em que está interessado. Você pode realizar essa tarefa definindo um ponto de interrupção condicional.

Você pode definir pontos de interrupção em condições diferentes, como o nome do thread ou a ID do thread. Pode ser útil definir a condição em dados que você sabe que são exclusivos de cada thread. Essa abordagem é comum durante a depuração, quando você está mais interessado em algum valor de dados específico do que em um thread específico.

  1. Clique com o botão direito do mouse no ponto de interrupção criado anteriormente e selecione Condições.

  2. Na janela Configurações do Ponto de Interrupção, insira data == 5 para a expressão condicional.

    Ponto de interrupção condicional

    Dica

    Se você estiver mais interessado em um thread específico, use um nome de thread ou ID de thread para a condição. Para fazer isso na janela Configurações do Ponto de Interrupção, selecione Filtrar em vez de Expressão condicional e siga as dicas de filtro. Talvez você queira nomear seus threads no código do aplicativo, pois as IDs dos threads são alteradas quando você reinicia o depurador.

  3. Feche a janela Configurações do Ponto de Interrupção.

  4. Selecione o botão Reiniciar Reiniciar Aplicativo para reiniciar sua sessão de depuração.

    Você deve interromper o código no thread em que o valor da variável de dados é 5. Na janela Inspeção Paralela, procure a seta amarela que indica o contexto atual do depurador.

  5. Agora, você pode fazer step-over do código (F10) e step-in do código (F11) e seguir a execução do thread único.

    Desde que a condição do ponto de interrupção seja exclusiva da thread e o depurador não atinja nenhum outro ponto de interrupção em outras threads (talvez seja necessário desabilitá-los), você pode passar por cima do código e entrar no código sem alternar para outras threads.

    Observação

    Quando você avança o depurador, todos os threads são executados. No entanto, o depurador não será interrompido em código em outros threads, a menos que um dos outros threads atinja um ponto de interrupção.