Tarefas periódicas e temporizadores
Agende trabalho recorrente com PeriodicTimer e System.Threading.Timer e gerencie o cancelamento com elegância.
Tarefas periódicas e temporizadores é uma aula grátis de C# Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C# Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C# Academy inclui 4 aulas no total.
Agendamento de trabalho recorrente
Muitos serviços em segundo plano precisam ser executados em uma agenda fixa — a cada 5 minutos, a cada hora ou diariamente. O .NET fornece vários mecanismos: PeriodicTimer (moderno), System.Threading.Timer (clássico) e o Quartz.NET (baseado em cron).
PeriodicTimer: a opção moderna
PeriodicTimer (introduzido no .NET 6) foi projetado para uso em métodos async. Ele dispara exatamente conforme a agenda, sem retornos de chamada, e integra-se de forma limpa ao cancelamento.
public class MetricsCollectorService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
while (await timer.WaitForNextTickAsync(ct))
{
// Runs every 60 seconds
await CollectMetricsAsync(ct);
}
// Loop exits cleanly when ct is cancelled
}
}PeriodicTimer versus loop com Task.Delay
PeriodicTimer é melhor que um loop com Task.Delay: ele dispara em intervalos fixos a partir da hora de início (não da hora de início + tempo de processamento), evitando desvios ao longo de muitas iterações.
// Task.Delay loop — drifts! (interval = delay + work time)
while (!ct.IsCancellationRequested)
{
await DoWorkAsync(ct); // takes 2 seconds
await Task.Delay(5000, ct); // actual period: 7 seconds
}
// PeriodicTimer — no drift
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
while (await timer.WaitForNextTickAsync(ct))
{
await DoWorkAsync(ct); // period is still 5 seconds from start
}System.Threading.Timer
System.Threading.Timer é um temporizador clássico do conjunto de linhas de execução que usa um retorno de chamada. Ele oferece suporte aos modos periódico e de execução única. Atenção: o retorno de chamada é executado em uma linha de execução do conjunto, sem suporte a async.
public class LegacyTimerService : BackgroundService, IDisposable
{
private Timer? _timer;
protected override Task ExecuteAsync(CancellationToken ct)
{
// Fire immediately then every 10 seconds
_timer = new Timer(DoWork, null,
dueTime: TimeSpan.Zero,
period: TimeSpan.FromSeconds(10));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
// Runs on thread pool — avoid async void here
Console.WriteLine($"Timer fired: {DateTime.UtcNow}");
}
public override void Dispose() { _timer?.Dispose(); base.Dispose(); }
}Execução atrasada de disparo único
Use PeriodicTimer com um único disparo ou combine CancellationTokenSource.CancelAfter com Task.Delay para executar uma operação atrasada de disparo único.
// Run once after a delay
public async Task ScheduleOnceAsync(TimeSpan delay, Func<Task> action, CancellationToken ct)
{
await Task.Delay(delay, ct);
await action();
}
// Or with CancellationToken:
var cts = new CancellationTokenSource(TimeSpan.FromHours(1));
try
{
await Task.Delay(Timeout.Infinite, cts.Token);
}
catch (OperationCanceledException)
{
await RunScheduledJobAsync();
}PeriodicTimer com tratamento de erros
Envolva sempre o trabalho de cada disparo em try/catch para que uma única falha não impeça o temporizador de disparar novamente no próximo ciclo.
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
while (await timer.WaitForNextTickAsync(ct))
{
try
{
await SyncInventoryAsync(ct);
}
catch (Exception ex)
{
// Log but don't crash — next tick will retry
_logger.LogError(ex, "Inventory sync failed");
}
}
}Alinhamento com limites do relógio
Para executar em horários específicos (por exemplo, a cada hora em ponto), calcule o atraso inicial até o próximo limite antes de iniciar o temporizador.
protected override async Task ExecuteAsync(CancellationToken ct)
{
// Wait until the next full hour
var now = DateTime.UtcNow;
var nextHour = now.Date.AddHours(now.Hour + 1);
var initialDelay = nextHour - now;
await Task.Delay(initialDelay, ct);
// Then tick every hour exactly
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
await RunJobAsync(ct); // run at the first tick
while (await timer.WaitForNextTickAsync(ct))
await RunJobAsync(ct);
}Proteção contra disparos simultâneos
Se o trabalho puder demorar mais que um período do temporizador, proteja contra a execução simultânea com um SemaphoreSlim ou ignore o disparo se o trabalho ainda estiver em execução.
private readonly SemaphoreSlim _lock = new(1, 1);
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
while (await timer.WaitForNextTickAsync(ct))
{
// Skip if previous tick is still running
if (!await _lock.WaitAsync(0, ct))
{
_logger.LogWarning("Skipping tick — previous still running");
continue;
}
try { await DoWorkAsync(ct); }
finally { _lock.Release(); }
}
}Jitter para serviços distribuídos
Quando várias instâncias de um serviço são iniciadas simultaneamente, adicionar um jitter aleatório ao atraso inicial evita problemas de efeito manada nos recursos compartilhados.
protected override async Task ExecuteAsync(CancellationToken ct)
{
// Add jitter: 0-5 seconds random delay on startup
var jitter = TimeSpan.FromSeconds(Random.Shared.NextDouble() * 5);
await Task.Delay(jitter, ct);
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
while (await timer.WaitForNextTickAsync(ct))
await RunJobAsync(ct);
}Exemplo real: serviço de atualização de cache
Um serviço periódico que atualiza um cache distribuído a cada 10 minutos, com jitter e recuperação de erros.
public class CacheRefreshService : BackgroundService
{
private readonly IServiceScopeFactory _factory;
private readonly ILogger<CacheRefreshService> _log;
protected override async Task ExecuteAsync(CancellationToken ct)
{
// Spread startups across instances
await Task.Delay(TimeSpan.FromSeconds(Random.Shared.Next(0, 30)), ct);
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(10));
while (await timer.WaitForNextTickAsync(ct))
{
try
{
using var scope = _factory.CreateScope();
var cache = scope.ServiceProvider.GetRequiredService<ICacheService>();
await cache.RefreshAllAsync(ct);
_log.LogInformation("Cache refreshed at {Time}", DateTime.UtcNow);
}
catch (Exception ex) { _log.LogError(ex, "Cache refresh failed"); }
}
}
}Verificação rápida
Por que PeriodicTimer é preferível a um loop com Task.Delay para tarefas recorrentes?
Recapitulação: tarefas periódicas e temporizadores
Principais conclusões:
PeriodicTimer: assíncrono, ciente do cancelamento, sem desvios — a opção modernaSystem.Threading.Timer: baseado em retorno de chamada, adequado para integração com código legado- Envolva o trabalho de cada disparo em try/catch para que os erros não interrompam o temporizador
- Adicione jitter para instâncias distribuídas a fim de evitar o efeito manada
- Use
SemaphoreSlimpara ignorar um disparo se o anterior ainda estiver em execução - Calcule o atraso inicial para alinhar os disparos aos limites do relógio
Perguntas Frequentes
A aula “Tarefas periódicas e temporizadores” é grátis?
Sim — o texto completo de “Tarefas periódicas e temporizadores” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C# Academy, atualize para CoddyKit PRO. O curso de C# Academy inclui 4 aulas no total.
O que vou aprender em “Tarefas periódicas e temporizadores”?
Agende trabalho recorrente com PeriodicTimer e System.Threading.Timer e gerencie o cancelamento com elegância. Você pratica C# Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar C# Academy?
Nenhuma experiência prévia é necessária. C# Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Tarefas periódicas e temporizadores”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de C# Academy?
Sim. Cada aula de C# Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- IHostedService e BackgroundService
- Projetos de Worker Service
- Tarefas periódicas e temporizadores
- Trabalhos agendados com Quartz.NET