C# Academy · Oppitunti

Toistuvat tehtävät ja ajastimet

Ajoittakaa toistuvaa työtä PeriodicTimerillä ja System.Threading.Timerillä sekä hallinnoikaa peruutusta hallitusti.

Oppitunti 3/412 vaihetta

Toistuvat tehtävät ja ajastimet on ilmainen C# Academy-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu C# Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. C# Academy-kurssilla on yhteensä 4 oppituntia.

Toistuvien tehtävien ajastaminen

Monien taustapalveluiden on suoritettava tehtäviä kiinteän aikataulun mukaan — viiden minuutin välein, tunneittain tai päivittäin. .NET tarjoaa tähän useita mekanismeja: PeriodicTimer (nykyaikainen), System.Threading.Timer (perinteinen) ja Quartz.NET (cron-pohjainen).

PeriodicTimer: nykyaikainen valinta

PeriodicTimer (esiteltiin .NET 6:ssa) on suunniteltu käytettäväksi async-metodeissa. Se laukeaa täsmällisesti aikataulun mukaan ilman callback-kutsuja ja integroituu siististi peruutukseen.

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 verrattuna Task.Delay-silmukkaan

PeriodicTimer on parempi kuin Task.Delay-silmukka: se laukeaa kiintein aikavälein aloitusajasta laskettuna (ei aloitusajan ja käsittelyajan summasta), mikä estää ajautumisen useiden iteraatioiden aikana.

// 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 on perinteinen callback-kutsua käyttävä thread pool -ajastin. Se tukee sekä toistuvaa että kertaluonteista toimintatilaa. Olkaa tarkkana: callback suoritetaan thread pool -säikeessä ilman asynkronoinnin tukea.

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

Kertaluonteinen viivästetty suoritus

Käyttäkää PeriodicTimer-ajastinta yhdellä tikillä tai yhdistäkää CancellationTokenSource.CancelAfter ja Task.Delay kertaluonteiseen viivästettyyn suoritukseen.

// 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 ja virheenkäsittely

Ympäröikää jokaisen tikin työ aina try/catch-rakenteella, jotta yksittäinen virhe ei estä ajastinta laukeamasta uudelleen seuraavalla kierroksella.

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

Kellonaikojen rajakohtiin kohdistaminen

Jos tehtävä halutaan suorittaa tiettyinä kellonaikoina, esimerkiksi tasatunnein, laskekaa ensin seuraavaan rajakohtaan kuluva viive ennen ajastimen käynnistämistä.

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

Samanaikaisten tikin suoritusten estäminen

Jos työ voi kestää yhtä ajastinjaksoa pidempään, estäkää samanaikainen suoritus SemaphoreSlim-olion avulla tai ohittakaa tikki, jos edellinen suoritus on vielä käynnissä.

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 hajautetuissa palveluissa

Kun useita palveluinstansseja käynnistyy samanaikaisesti, satunnaisen jitterin lisääminen alkuviiveeseen ehkäisee jaettuihin resursseihin kohdistuvia thundering herd -ongelmia.

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

Käytännön esimerkki: välimuistin päivityspalvelu

Ajastettu palvelu, joka päivittää hajautetun välimuistin 10 minuutin välein ja sisältää jitterin sekä virheistä palautumisen.

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

Pikatarkistus

Miksi PeriodicTimeria suositaan Task.Delay-silmukan sijaan toistuvissa tehtävissä?

Kertaus: toistuvat tehtävät ja ajastimet

Keskeiset opit:

  • PeriodicTimer: asynkroninen, peruutustietoinen ja ajautumaton — nykyaikainen valinta
  • System.Threading.Timer: callback-pohjainen, sopii hyvin vanhan koodin integrointiin
  • Ympäröikää jokaisen tikin työ try/catch-rakenteella, jotta virheet eivät pysäytä ajastinta
  • Lisätkää hajautettuihin instansseihin jitteriä thundering herd -ongelmien välttämiseksi
  • Käyttäkää SemaphoreSlim-oliota tikin ohittamiseen, jos edellinen suoritus on vielä käynnissä
  • Laskekaa alkuviive, jotta tikit osuvat kellonaikojen rajakohtiin
Aloita maksutta

Opi C# tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
93
Oppitunnit
346

Usein kysytyt kysymykset

Onko oppitunti ”Toistuvat tehtävät ja ajastimet” ilmainen?

Kyllä – oppitunnin ”Toistuvat tehtävät ja ajastimet” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko C# Academy-kurssin, päivitä CoddyKit PROhon. C# Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Toistuvat tehtävät ja ajastimet”?

Ajoittakaa toistuvaa työtä PeriodicTimerillä ja System.Threading.Timerillä sekä hallinnoikaa peruutusta hallitusti. Harjoittelet C# Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni C# Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin C# Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.

Kuinka kauan ”Toistuvat tehtävät ja ajastimet”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä C# Academy-oppitunnilla?

Kyllä. Jokainen C# Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. IHostedService ja BackgroundService
  2. Worker Service -projektit
  3. Toistuvat tehtävät ja ajastimet
  4. Quartz.NET-ajastetut työt
← Takaisin: C# Academy