Tâches périodiques et minuteurs
Planifiez des tâches récurrentes avec PeriodicTimer et System.Threading.Timer, puis gérez l’annulation avec élégance.
Tâches périodiques et minuteurs est une leçon C# Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage C# Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours C# Academy comprend 4 leçons au total.
Planification de tâches récurrentes
De nombreux services d’arrière-plan doivent s’exécuter selon une planification fixe : toutes les 5 minutes, toutes les heures ou tous les jours. .NET fournit plusieurs mécanismes : PeriodicTimer (moderne), System.Threading.Timer (classique) et Quartz.NET (fondé sur cron).
PeriodicTimer : le choix moderne
PeriodicTimer (introduit dans .NET 6) est conçu pour être utilisé dans des méthodes async. Il se déclenche exactement selon la planification, sans rappels, et s’intègre proprement à l’annulation.
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 contre une boucle Task.Delay
PeriodicTimer est préférable à une boucle Task.Delay : il se déclenche à intervalles fixes à partir de l’heure de début (et non à partir de l’heure de début augmentée de la durée du traitement), ce qui évite la dérive au fil des itérations.
// 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 est un minuteur classique du pool de threads qui utilise un rappel. Il prend en charge les modes périodique et ponctuel. Soyez prudent : le rappel s’exécute sur un thread du pool sans prise en charge de 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(); }
}Exécution différée ponctuelle
Utilisez PeriodicTimer avec un seul déclenchement, ou combinez CancellationTokenSource.CancelAfter avec Task.Delay, pour une exécution différée ponctuelle.
// 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 avec gestion des erreurs
Entourez toujours le travail effectué à chaque déclenchement d’un bloc try/catch afin qu’un échec isolé n’empêche pas le minuteur de se déclencher à nouveau au cycle suivant.
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");
}
}
}Alignement sur les limites de l’horloge
Pour exécuter une tâche à des heures précises (par exemple, toutes les heures à l’heure pleine), calculez le délai initial jusqu’à la prochaine limite avant de démarrer le minuteur.
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);
}Protection contre les déclenchements concurrents
Si le travail peut prendre plus de temps qu’une période du minuteur, protégez-vous contre les exécutions concurrentes avec un SemaphoreSlim, ou ignorez le déclenchement si une exécution est encore en cours.
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(); }
}
}Gigue pour les services distribués
Lorsque plusieurs instances d’un service démarrent simultanément, l’ajout d’une gigue aléatoire au délai initial évite les effets de troupeau sur les ressources partagées.
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);
}Cas réel : service d’actualisation du cache
Un service périodique qui actualise un cache distribué toutes les 10 minutes, avec gigue et récupération après erreur.
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"); }
}
}
}Vérification rapide
Pourquoi PeriodicTimer est-il préférable à une boucle Task.Delay pour les tâches récurrentes ?
Récapitulatif : tâches périodiques et minuteurs
Points essentiels :
PeriodicTimer: async, sensible à l’annulation, sans dérive — le choix moderneSystem.Threading.Timer: fondé sur un rappel, adapté à l’intégration avec du code existant- Entourez le travail effectué à chaque déclenchement d’un bloc try/catch afin que les erreurs n’arrêtent pas le minuteur
- Ajoutez une gigue aux instances distribuées pour éviter les effets de troupeau
- Utilisez
SemaphoreSlimpour ignorer un déclenchement si le précédent est encore en cours - Calculez le délai initial afin d’aligner les déclenchements sur les limites de l’horloge
Questions Fréquemment Posées
La leçon « Tâches périodiques et minuteurs » est-elle gratuite ?
Oui — le texte complet de « Tâches périodiques et minuteurs » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours C# Academy, passe à CoddyKit PRO. Le cours C# Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Tâches périodiques et minuteurs » ?
Planifiez des tâches récurrentes avec PeriodicTimer et System.Threading.Timer, puis gérez l’annulation avec élégance. Tu pratiques C# Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer C# Academy ?
Aucune expérience préalable n'est requise. C# Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.
Combien de temps prend la leçon « Tâches périodiques et minuteurs » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon C# Academy ?
Oui. Chaque leçon C# Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- IHostedService et BackgroundService
- Projets Worker Service
- Tâches périodiques et minuteurs
- Tâches planifiées avec Quartz.NET