CodeGym /Cursos /C# SELF /Ciclo de vida de uma thread e como controlá-la

Ciclo de vida de uma thread e como controlá-la

C# SELF
Nível 55 , Lição 2
Disponível

1. Introdução

Imagine que uma thread é um funcionário incansável a quem você delega um trabalho. O funcionário pode estar dormindo (ainda não começou), trabalhando duro (seu método está sendo executado), esperando você dar uma nova tarefa (idle), ou ter terminado o trabalho (concluído).

No C# (e no .NET em geral) o ciclo de vida de uma thread consiste em vários estados:

  • Unstarted — a thread foi criada, mas ainda não foi iniciada.
  • Running — a thread está em execução.
  • WaitSleepJoin — a thread está temporariamente inativa (por exemplo, esperando um sinal ou "dormindo").
  • Stopped — a thread completou a tarefa e terminou.

Você pode visualizar esse ciclo com o seguinte diagrama:

stateDiagram-v2
    [*] --> Unstarted
    Unstarted --> Running: Start()
    Running --> WaitSleepJoin: Wait/Sleep/Join
    WaitSleepJoin --> Running: Sinal recebido/Tempo esgotou
    Running --> Stopped: Método terminou
    WaitSleepJoin --> Stopped: Método terminou
    Stopped --> [*]

Tudo começa com a criação do objeto Thread, mas até você chamar Start() a thread "cochila" no estado Unstarted. Depois do Start() — começa a ação, a thread passa para Running. Se dentro do código a thread chamar Thread.Sleep ou ficar esperando algo (por exemplo, Monitor.Wait), ela vai para um estado especial de espera. Assim que o método atribuído à thread termina, a thread "morre", deixa de existir e não "ressuscita". É um bilhete só de ida.

2. Prática: ciclo de vida de uma thread simples

Vamos ver o exemplo clássico:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // Criamos a thread — por enquanto só planejamos o trabalho
        Thread worker = new Thread(DoWork);

        Console.WriteLine($"Estado da thread após criação: {worker.ThreadState}");

        // Iniciamos a thread
        worker.Start();
        Console.WriteLine($"Estado da thread após start: {worker.ThreadState}");

        // Deixamos a thread principal dormir um pouco para a worker ter tempo de trabalhar
        Thread.Sleep(100);

        Console.WriteLine($"Estado da thread (mais tarde): {worker.ThreadState}");

        // Esperamos que worker termine (fazer Join)
        worker.Join();

        Console.WriteLine($"Estado da thread após término: {worker.ThreadState}");
        Console.WriteLine("Thread principal finalizada");
    }

    static void DoWork()
    {
        Console.WriteLine("Thread de trabalho começou!");
        Thread.Sleep(500);
        Console.WriteLine("Thread de trabalho terminou!");
    }
}

O que o programa imprime?

  1. Após criar a thread — o estado será Unstarted.
  2. Após o start — normalmente imediatamente Running (mas pode ser Running | Background).
  3. Durante a execução — o estado pode ser Running, ou WaitSleepJoin se a thread estiver "dormindo".
  4. Depois que o método termina — o estado vira Stopped.

Esse código é uma ótima ferramenta para entender em que estado sua thread pode estar. Brinque com os delays e veja como o estado muda.

3. Controle da thread: métodos principais

Iniciar: Start()

É óbvio, mas importante repetir: crie a thread — inicie com Start(). E só dá pra iniciar uma vez: tentar chamar Start() de novo vai gerar uma exceção ThreadStateException.

Thread t = new Thread(MyMethod);
t.Start();   // OK
t.Start();   // Erro!

Esperar término: Join()

Às vezes você precisa aguardar até a thread terminar antes de continuar. Para isso existe Join().

Thread t = new Thread(MyMethod);
t.Start();
t.Join(); // Bloqueia a thread atual até t terminar

Se houver várias threads, você pode chamar Join() para cada — a thread principal vai esperar até todas as "workers" terminarem.

Variante: existe a sobrecarga Join(int millisecondsTimeout), que espera só o tempo especificado e depois continua.

// Espera no máximo 2 segundos
if (t.Join(2000))
    Console.WriteLine("Thread terminou a tempo");
else
    Console.WriteLine("Cansamos de esperar...");

Parada forçada: por que é ruim

Em versões antigas do .NET havia o método Thread.Abort(), que permitia "matar" a thread na hora. Hoje quase não se usa — é perigoso e pode deixar o programa em estado inconsistente. A filosofia do .NET é: a thread deve terminar voluntariamente. Você não "mata" o funcionário — você educadamente indica que o expediente acabou.

4. Como "parar" uma thread corretamente

A forma mais correta e segura de parar uma thread é usar uma flag de cancelamento ou um sinalizador que a thread verifica periodicamente.

class Worker
{
    private volatile bool shouldStop = false;

    public void DoWork()
    {
        while (!shouldStop)
        {
            Console.WriteLine("Estou trabalhando!");
            Thread.Sleep(300);
        }

        Console.WriteLine("Thread finalizando o trabalho por comando.");
    }

    public void RequestStop()
    {
        shouldStop = true;
    }
}

Uso:

Worker w = new Worker();
Thread t = new Thread(w.DoWork);
t.Start();

// Esperamos um pouco
Thread.Sleep(1000);

// Pedimos para a thread terminar
w.RequestStop();
t.Join(); // Espera a thread terminar

Ponto importante: volatile

A palavra-chave volatile diz ao compilador e à CPU: "Não faça cache deste campo, sempre leia o valor atual!" Isso é importante para a thread ver a atualização da flag de parada. Sem isso (ou sem outros mecanismos de sincronização) a thread pode nunca perceber suas mudanças.

5. Transição para estados de espera e sono

Às vezes a thread fica temporariamente sem fazer trabalho — ela ou dorme, ou espera.

Sono: Thread.Sleep

Quando você quer dar um descanso à thread ou desacelerar a execução (por exemplo, para não sobrecarregar a CPU), use Thread.Sleep(milliseconds).

// Thread dorme por 2 segundos
Thread.Sleep(2000);

Durante o sono a thread não executa trabalho nenhum.

Espera / Join

Quando a thread principal espera a filha terminar (Join), a principal fica "em pausa". Da mesma forma, se uma thread espera pela liberação de um recurso (por exemplo, usando monitores ou outros primitivos de sincronização), ela entra num estado especial de espera.

6. Controlando se a thread é background

No .NET as threads podem ser de dois tipos: foreground (primeiro plano) e background (plano de fundo). A diferença é simples:

  • Se no processo só restarem threads background, o processo termina automaticamente.
  • A thread principal e todas as threads de foreground precisam terminar para que o processo pare de rodar.

Você pode marcar explicitamente uma thread como background:

Thread t = new Thread(SomeMethod);
t.IsBackground = true; // Marcou como background
t.Start();

Exemplo prático — Demon vs Thread normal

Thread t = new Thread(() =>
{
    while (true)
    {
        Console.WriteLine("Sou um fantasma (background), não consigo ser parado!");
        Thread.Sleep(500);
    }
});
t.IsBackground = true; // Tornando background
t.Start();

Thread.Sleep(1200);
Console.WriteLine("Thread principal termina");
// Depois do fim do Main — o processo morre, e nossa thread eterna também some

Após o fim do Main — o processo finaliza; threads background são terminadas automaticamente.

7. Dicas úteis

O que não fazer com threads

  • Não tente "reiniciar" uma thread. O objeto Thread vive uma vez: quando seu método terminar — a thread morreu, e chamar Start() de novo causará erro.
  • Não pare forçadamente threads alheias com métodos como Thread.Abort() ou Thread.Suspend() — são obsoletos e perigosos.
  • Não ignore o fim do trabalho da thread. Se a thread trabalha com arquivos ou recursos, libere-os corretamente antes de encerrar a thread.

Checando estado e controlando o ciclo de vida

if (t.IsAlive)
{
    Console.WriteLine("A thread ainda está viva");
}
else
{
    Console.WriteLine("A thread terminou");
}

IsAlivetrue enquanto a thread está executando seu método; depois de terminar — false.

Ciclo de vida de uma thread simples no .NET

Estado Como entrar O que significa? Como sair
Unstarted
new Thread(...)
Thread criada, não iniciada Chamar Start()
Running
Start()
Thread executando trabalho Finalizar o método
WaitSleepJoin Sleep(), Join(), esperando Thread temporariamente inativa Espera termina
Stopped Método da thread terminou Thread "morreu" Não sai — fim

FAQ sobre gerenciamento do ciclo de vida

Pergunta: É possível matar uma thread por comando?
Resposta: Não e nem precisa; threads devem cuidar do próprio término. Use flags de cancelamento.

Pergunta: Dá pra reutilizar o objeto Thread?
Resposta: Não. Crie um novo objeto para um novo trabalho.

Pergunta: O que acontece se a thread principal terminar, mas a filha continuar rodando?
Resposta: Se a thread filha for background (IsBackground == true), o aplicativo termina. Se não for — o processo fica vivo até todas as threads terminarem.

Pergunta: Como liberar recursos corretamente se a thread terminar por cancelamento?
Resposta: Use blocos try...finally dentro do método da thread, para garantir que recursos sejam liberados em qualquer caso.

8. Erros comuns e como evitá-los ao trabalhar com threads

Erro #1: reutilizar o mesmo objeto Thread.
Não dá pra iniciar o mesmo objeto de thread mais de uma vez. Depois que a thread termina, não é possível reiniciá-la — isso causará exceção.

Erro #2: liberação incorreta de recursos externos na thread.
Se a thread mexe com arquivos, rede ou outros recursos, assegure o fechamento e liberação corretos. Recomenda-se usar finally ou a construção using para evitar leaks e deadlocks.

Erro #3: criar threads em excesso.
Muitos threads tornam a depuração difícil e podem degradar performance. Uma thread a mais pode significar tempo extra procurando e consertando bugs inesperados.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION