1. O que é "fire and forget"?
Em programação o termo fire and forget significa iniciar uma tarefa sem esperar que ela termine. No mundo C# e .NET isso normalmente é feito com tarefas Task que são iniciadas, mas não aguardadas (await), sem manter referência e efetivamente esquecidas.
// O botão inicia uma tarefa em background, mas em nenhum lugar ela é await-ada.
button.Click += (s, e) =>
{
Task.Run(() => DolgayaOperaciya());
};
Parece atraente: "deixa rodar em background enquanto eu faço outra coisa". Mas com essa abordagem, se ocorrer uma exceção dentro da tarefa, ninguém vai saber a tempo — ela some silenciosamente.
2. Como funciona o tratamento de exceções em Task
Clássico: await e tratamento de erros
A maneira padrão de trabalhar com tarefas assíncronas é via await. Se houver um erro na tarefa, ele será lançado no ponto do await:
try
{
await SomeOperationAsync(); // se aqui dentro lançar Exception, ela cairá no catch
}
catch(Exception ex)
{
Console.WriteLine("Ops! A tarefa lançou um erro: " + ex.Message);
}
Ou seja, quando você espera a conclusão da tarefa, você não perde a exceção.
Mas tarefas "fire and forget" não são aguardadas!
public void ZapustitBezOzhidaniya()
{
// A tarefa roda por si só. Ninguém a espera...
Task.Run(() => {
// Em algum ponto ocorre problema:
throw new InvalidOperationException("Ih, deu ruim!");
});
// O método terminou, a tarefa roda silenciosamente em background.
}
Se ocorrer uma exceção dentro dessa tarefa, ela não será lançada no thread principal. A aplicação continua rodando como se nada tivesse acontecido.
Importante
No .NET, uma tarefa com exceção não tratada fica no estado Faulted. Mas se você não a aguarda (await, .Result, .Wait() etc.), ninguém vai ler a exceção e ela não vai aparecer no código chamador.
O que realmente acontece "por baixo do capô"?
Para tarefas que ninguém aguarda, resta uma única chance de serem notadas — o evento TaskScheduler.UnobservedTaskException. Ele dispara quando o garbage collector (GC) encontra uma tarefa com exceção não observada. Mas isso não acontece imediatamente e nem onde você espera — não dá pra confiar nisso.
3. Demonstração: erro fire-and-forget
// Exemplo: iniciamos uma tarefa fire-and-forget direto do Main
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
FireAndForgetExample();
Console.WriteLine("Thread principal continua rodando...");
// Damos um tempo para a tarefa terminar
Task.Delay(2000).Wait();
}
static void FireAndForgetExample()
{
Task.Run(() =>
{
Console.WriteLine("Tarefa fire-and-forget começou!");
Task.Delay(500).Wait();
throw new InvalidOperationException("Erro dentro da tarefa fire-and-forget!");
});
}
}
Se você rodar esse código, ... nada de especial vai acontecer. O erro acontece, mas o programa não fica sabendo. Às vezes um aviso aparece na Output Window da IDE, mas o usuário não recebe informação.
Por que isso é perigoso em projetos reais?
- Bugs complexos que são difíceis de reproduzir ("às vezes não funciona — não sei por quê").
- Perda silenciosa de dados ou lógica (por exemplo, um email que não foi enviado).
- Em produção — falta de sinais sobre problemas, a menos que exista logging configurado.
4. Maneiras corretas de tratar erros em fire-and-forget
Log e tratamento de erros dentro da própria tarefa
O nível mínimo seguro é capturar exceções dentro da própria tarefa fire-and-forget:
Task.Run(() =>
{
try
{
// Seu código longo/perigoso
throw new InvalidOperationException("Algo deu errado!");
}
catch (Exception ex)
{
// Loga o erro ou informa o usuário
Console.WriteLine("Fire-and-forget: exceção capturada: " + ex.Message);
// Pode escrever em um arquivo de log, enviar para sistema de alerta etc.
}
});
Métodos assíncronos void (e por que não é uma boa)
async void DangerousFireAndForget()
{
// Algo perigoso
throw new Exception("Boom!");
}
Métodos async void são, na prática, fire-and-forget: não dá pra aguardá-los, eles não retornam Task. Exceções vindas deles vão para o handler global da aplicação (por exemplo, AppDomain.UnhandledException) e muitas vezes causam a queda do processo. Use async void só para event handlers — e mesmo assim com cuidado.
Usando helpers para tratar erros
É conveniente extrair o lançamento seguro de fire-and-forget para um wrapper:
// Método genérico para iniciar safe fire-and-forget
public static void RunSafeFireAndForget(Func<Task> taskFactory)
{
Task.Run(async () =>
{
try
{
await taskFactory();
}
catch (Exception ex)
{
// Loga a exceção
Console.WriteLine("Fire-and-forget (safe): " + ex);
// Pode enviar para sistema de monitoramento!
}
});
}
// Uso:
RunSafeFireAndForget(async () =>
{
await Task.Delay(1000);
throw new InvalidOperationException("Dentro do fire-and-forget!");
});
Exemplo do "mundo real": envio de email
// Botão de enviar email:
private void buttonSend_Click(object sender, EventArgs e)
{
Task.Run(() => SendEmail());
}
// Método de envio:
private void SendEmail()
{
try
{
// Aqui pode acontecer o envio real
throw new Exception("Servidor SMTP indisponível!");
}
catch (Exception ex)
{
// Logging
File.AppendAllText("errors.log", $"Erro ao enviar: {ex.Message}\n");
}
}
5. E o UnobservedTaskException?
Como último recurso, o .NET fornece o evento TaskScheduler.UnobservedTaskException. Ele é chamado se uma tarefa terminou com erro, ninguém a aguardou, e o objeto tarefa foi coletado pelo GC. Não confie nisso — é um mecanismo de "última chance".
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
Console.WriteLine("UnobservedTaskException global: " + e.Exception);
e.SetObserved(); // Não esqueça de chamar isso, senão a aplicação pode terminar de forma abrupta!
};
Mais detalhes: TaskScheduler.UnobservedTaskException.
6. Nuances úteis
Comparação esquemática de abordagens
| Método | Exceções tratadas? | Onde capturar erros | Risco de "perder" erro |
|---|---|---|---|
|
Sim | No código chamador | Baixo |
| Fire-and-forget sem try/catch | Não | Em nenhum lugar | Muito alto |
| Fire-and-forget com try/catch | Sim | Dentro da própria tarefa | Baixo (se você logar) |
| async void-method | Não (vai para global) | Handler global | Alto |
Como projetar fire-and-forget corretamente
- Se o resultado ou estado da tarefa é crítico — não faça fire-and-forget. Use await ou guarde a Task para aguardar depois.
- Fire-and-forget só faz sentido para tarefas realmente não críticas (por exemplo, enviar telemetria).
- Sempre encapsule fire-and-forget em um método próprio e capture/logue exceções.
- Para cenários de background mais complexos use filas e workers: Hangfire, Quartz.NET.
Aplicação prática e entrevistas
Em entrevistas frequentemente perguntam: "O que acontece se uma exceção ocorrer em uma tarefa fire-and-forget?" ou "Por que não se deve usar async void sempre?" A resposta certa: você é responsável pelo destino das exceções em tarefas em background — ou captura, loga e analisa, ou recebe bugs-fantasma.
Comparação entre "fire-and-forget" e await
| Cenário | Confiabilidade no tratamento de erros | Aplicabilidade |
|---|---|---|
| Await normal | Excelente | Onde for necessário resultado ou importante saber success/fail |
| Fire-and-forget | Ruim (se não tratar manualmente) | Apenas para tarefas realmente background e não críticas |
| Fire-and-forget com try/catch | Bom (se logar) | Tarefas background onde não precisa do resultado, mas é importante saber de falhas |
Na próxima aula vamos discutir tratamento de erros em tarefas paralelas que retornam múltiplos resultados. Por enquanto, lembre-se: se você atirou em alguma direção, verifique se a bala chegou no alvo!
7. Erros típicos ao trabalhar com tarefas fire-and-forget
Erro nº1: Ignorar exceções em fire-and-forget.
Iniciantes esperam que exceções "vão subir" sozinhas. Sem try-catch e logging elas se perdem, causando bugs indetectáveis.
Erro nº2: Usar async void fora de event handlers.
Esses métodos jogam exceções no handler global (por exemplo, AppDomain.UnhandledException), o que pode encerrar a aplicação abruptamente.
Erro nº3: Tratar exceções demais.
Capturar todas as exceções dentro da tarefa pode esconder problemas que seriam melhor tratados no código chamador, dificultando o debug.
Erro nº4: Ignorar logging.
Sem logging de erros em tarefas fire-and-forget é impossível saber sobre falhas, especialmente em produção.
GO TO FULL VERSION