Toistuvat tehtävät ja ajastimet
Ajoittakaa toistuvaa työtä PeriodicTimerillä ja System.Threading.Timerillä sekä hallinnoikaa peruutusta hallitusti.
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 valintaSystem.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
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
- IHostedService ja BackgroundService
- Worker Service -projektit
- Toistuvat tehtävät ja ajastimet
- Quartz.NET-ajastetut työt