0Pricing
C# Academy · Lektion

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 Wahl
  • System.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

  1. IHostedService und BackgroundService
  2. Worker-Service-Projekte
  3. Periodische Aufgaben und Timer
  4. Geplante Jobs mit Quartz.NET
← Zurück zu C# Academy