C# Academy · Les

Periodieke taken en timers

Plan terugkerend werk met PeriodicTimer en System.Threading.Timer en beheer annulering op een nette manier.

Les 3 van 412 stappen

Periodieke taken en timers is een gratis C# Academy-les op CoddyKit. Dit is les 3 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject C# Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus C# Academy bevat in totaal 4 lessen.

Terugkerend werk plannen

Veel achtergrondservices moeten volgens een vast schema worden uitgevoerd — elke 5 minuten, elk uur of dagelijks. .NET biedt verschillende mechanismen: PeriodicTimer (modern), System.Threading.Timer (klassiek) en Quartz.NET (op cron gebaseerd).

PeriodicTimer: de moderne keuze

PeriodicTimer (geïntroduceerd in .NET 6) is ontworpen voor gebruik in async-methoden. De timer wordt precies volgens schema geactiveerd, zonder callbacks, en werkt goed samen met annulering.

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 tegenover een Task.Delay-lus

PeriodicTimer is beter dan een lus met Task.Delay: de timer wordt met vaste tussenpozen vanaf de starttijd geactiveerd (niet vanaf de starttijd plus verwerkingstijd), waardoor afwijking na veel herhalingen wordt voorkomen.

// 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 is een klassieke threadpooltimer die een terugbelfunctie gebruikt. Deze ondersteunt zowel periodieke als eenmalige uitvoeringen. Let op: de terugbelfunctie wordt uitgevoerd op een threadpoolthread zonder ondersteuning voor 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(); }
}

Eenmalige vertraagde uitvoering

Gebruik PeriodicTimer met één tik, of combineer CancellationTokenSource.CancelAfter met Task.Delay, voor eenmalige vertraagde uitvoering.

// 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 met foutafhandeling

Wikkel het werk bij elke tik altijd in try/catch, zodat één fout niet verhindert dat de timer in de volgende cyclus opnieuw tikt.

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");
        }
    }
}

Uitlijnen op klokgrenzen

Als je het werk op specifieke tijdstippen wilt uitvoeren (bijvoorbeeld elk uur op het hele uur), bereken je de aanvankelijke vertraging tot de volgende klokgrens voordat je de timer start.

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);
}

Bescherming tegen gelijktijdige tikken

Als werk langer kan duren dan één timerperiode, voorkom je gelijktijdige uitvoering met een SemaphoreSlim of sla je de tik over als het vorige werk nog loopt.

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(); }
    }
}

Willekeurige spreiding voor gedistribueerde services

Wanneer meerdere instanties van een service gelijktijdig starten, voorkomt een willekeurige afwijking in de aanvankelijke vertraging stormloopproblemen bij gedeelde resources.

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);
}

Uit de praktijk: cacheservice vernieuwen

Een periodieke service die elke 10 minuten een gedistribueerde cache vernieuwt, met willekeurige spreiding en foutherstel.

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"); }
        }
    }
}

Korte controle

Waarom heeft PeriodicTimer de voorkeur boven een Task.Delay-lus voor terugkerende taken?

Samenvatting: periodieke taken en timers

Belangrijkste punten:

  • PeriodicTimer: async, houdt rekening met annulering en veroorzaakt geen afwijking — de moderne keuze
  • System.Threading.Timer: gebaseerd op terugbelfuncties, geschikt voor integratie met oudere code
  • Wikkel het werk bij elke tik in try/catch, zodat fouten de timer niet stoppen
  • Voeg willekeurige spreiding toe voor gedistribueerde instanties om stormloop te voorkomen
  • Gebruik SemaphoreSlim om een tik over te slaan als de vorige nog wordt uitgevoerd
  • Bereken de aanvankelijke vertraging om de tikken op klokgrenzen uit te lijnen
Gratis beginnen

Leer C# met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
93
Lessen
346

Veelgestelde vragen

Is de les “Periodieke taken en timers” gratis?

Ja — de volledige tekst van “Periodieke taken en timers” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus C# Academy wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus C# Academy bevat in totaal 4 lessen.

Wat leer ik in “Periodieke taken en timers”?

Plan terugkerend werk met PeriodicTimer en System.Threading.Timer en beheer annulering op een nette manier. Je oefent met C# Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met C# Academy te beginnen?

Ervaring vooraf is niet nodig. C# Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 3 van 4.

Hoe lang duurt de les “Periodieke taken en timers”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over C# Academy?

Ja. Elke les over C# Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. IHostedService en BackgroundService
  2. Worker Service-projecten
  3. Periodieke taken en timers
  4. Geplande taken met Quartz.NET
← Terug naar C# Academy