Periodische Aufgaben und Timer
Planen Sie wiederkehrende Arbeit mit PeriodicTimer und System.Threading.Timer und behandeln Sie Abbrüche ordnungsgemäß.
Periodische Aufgaben und Timer ist eine kostenlose C# Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des C# Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.
Wiederkehrende Aufgaben planen
Viele Hintergrunddienste müssen nach einem festen Zeitplan ausgeführt werden – alle 5 Minuten, stündlich oder täglich. .NET bietet dafür mehrere Mechanismen: PeriodicTimer (modern), System.Threading.Timer (klassisch) und Quartz.NET (Cron-basiert).
PeriodicTimer: Die moderne Wahl
PeriodicTimer (seit .NET 6 verfügbar) wurde für die Verwendung in async-Methoden entwickelt. Er wird exakt nach Zeitplan ausgelöst, benötigt keine Callbacks und lässt sich sauber mit Abbruchvorgängen integrieren.
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 im Vergleich zu einer Task.Delay-Schleife
PeriodicTimer ist besser geeignet als eine Task.Delay-Schleife: Er wird in festen Intervallen ab dem Startzeitpunkt ausgelöst (nicht erst nach Startzeitpunkt plus Verarbeitungszeit), wodurch sich über viele Iterationen keine Zeitabweichung aufbaut.
// 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 ist ein klassischer Threadpool-Timer, der einen Callback verwendet. Er unterstützt sowohl periodische als auch einmalige Ausführungen. Seien Sie vorsichtig: Der Callback wird in einem Threadpool-Thread ohne async-Unterstützung ausgeführt.
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(); }
}Einmalige verzögerte Ausführung
Verwenden Sie für eine einmalige verzögerte Ausführung PeriodicTimer mit einem einzigen Tick oder CancellationTokenSource.CancelAfter zusammen mit Task.Delay.
// 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 mit Fehlerbehandlung
Umschließen Sie die Arbeit pro Tick immer mit try/catch, damit ein einzelner Fehler nicht verhindert, dass der Timer im nächsten Zyklus erneut ausgelöst wird.
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");
}
}
}An Uhrzeitgrenzen ausrichten
Wenn eine Aufgabe zu bestimmten Uhrzeiten ausgeführt werden soll (z. B. stündlich zur vollen Stunde), berechnen Sie vor dem Start des Timers die anfängliche Verzögerung bis zur nächsten Zeitgrenze.
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);
}Schutz vor gleichzeitigen Ticks
Wenn die Arbeit länger als ein Timerintervall dauern kann, schützen Sie sich mit einem SemaphoreSlim vor einer gleichzeitigen Ausführung oder überspringen Sie den Tick, falls die vorherige Ausführung noch läuft.
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 für verteilte Dienste
Wenn mehrere Instanzen eines Dienstes gleichzeitig gestartet werden, verhindert ein zufälliger Jitter bei der anfänglichen Verzögerung Thundering-Herd-Probleme bei gemeinsam genutzten Ressourcen.
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);
}Praxisbeispiel: Dienst zum Aktualisieren des Caches
Ein periodischer Dienst, der alle 10 Minuten einen verteilten Cache aktualisiert und dabei Jitter sowie Fehlerbehebung verwendet.
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"); }
}
}
}Kurztest
Warum wird PeriodicTimer für wiederkehrende Aufgaben einer Task.Delay-Schleife vorgezogen?
Zusammenfassung: Periodische Aufgaben und Timer
Wichtigste Erkenntnisse:
PeriodicTimer: async, abbruchfähig, ohne Zeitabweichung – die moderne WahlSystem.Threading.Timer: Callback-basiert, gut für die Integration in Legacy-Code geeignet- Umschließen Sie die Arbeit pro Tick mit try/catch, damit Fehler den Timer nicht stoppen
- Fügen Sie bei verteilten Instanzen Jitter hinzu, um den Thundering-Herd-Effekt zu vermeiden
- Verwenden Sie
SemaphoreSlim, um einen Tick zu überspringen, wenn der vorherige noch läuft - Berechnen Sie die anfängliche Verzögerung, um die Ticks an den Uhrzeitgrenzen auszurichten
Häufig gestellte Fragen
Ist die Lektion „Periodische Aufgaben und Timer“ kostenlos?
Ja — der vollständige Text von „Periodische Aufgaben und Timer“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des C# Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der C# Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Periodische Aufgaben und Timer“?
Planen Sie wiederkehrende Arbeit mit PeriodicTimer und System.Threading.Timer und behandeln Sie Abbrüche ordnungsgemäß. Du übst C# Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um C# Academy zu starten?
Keine Vorkenntnisse erforderlich. C# Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.
Wie lange dauert die Lektion „Periodische Aufgaben und Timer“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser C# Academy-Lektion Code schreiben und ausführen?
Ja. Jede C# Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- IHostedService und BackgroundService
- Worker-Service-Projekte
- Periodische Aufgaben und Timer
- Geplante Jobs mit Quartz.NET