Interval timer (production) — PrimeTime (NodaTime) stack
This example demonstrates registering both synchronous and asynchronous interval timer callbacks on IPrimeClock using the PrimeTime (NodaTime) package (KZDev.PrimeTime). The timer signatures use NodaTime Duration for due time and repeat interval — the superset adds these overloads on top of the shared TimeSpan API.
The snippet below is the body of the demo RunAsync method. It builds a minimal ServiceCollection, resolves an IPrimeClock, registers two timers (one synchronous, one asynchronous), waits for both to fire twice, and disposes both registrations.
ServiceCollection services = [];
services.AddPrimeClock();
await using ServiceProvider serviceProvider = services.BuildServiceProvider();
IPrimeClock primeClock = serviceProvider.GetRequiredService<IPrimeClock>();
Duration dueTime = _runMode == DemoRunMode.Long
? Duration.FromSeconds(1)
: Duration.FromMilliseconds(300);
Duration repeat = _runMode == DemoRunMode.Long
? Duration.FromSeconds(2)
: Duration.FromMilliseconds(700);
TaskCompletionSource syncCompleted = new(TaskCreationOptions.RunContinuationsAsynchronously);
TaskCompletionSource asyncCompleted = new(TaskCreationOptions.RunContinuationsAsynchronously);
int syncCount = 0;
int asyncCount = 0;
ScenarioConsole.WriteLine($"Starting sync interval timer (due: {dueTime}, repeat: {repeat}).");
IClockIntervalTimer syncTimer = primeClock.RegisterTimer(dueTime, repeat,
(context, callbackCancellationToken) =>
{
callbackCancellationToken.ThrowIfCancellationRequested();
int currentCount = Interlocked.Increment(ref syncCount);
ScenarioConsole.WriteLine($"sync interval callback #{currentCount} on registration {context.Registration.Id}");
if (currentCount >= 2)
{
syncCompleted.TrySetResult();
}
},
cancellationToken, state: "sync timer", timerOptions: null);
ScenarioConsole.WriteLine($"Starting async interval timer (due: {dueTime}, repeat: {repeat}).");
IClockIntervalTimer asyncTimer = primeClock.RegisterAsyncTimer(dueTime, repeat,
async (context, callbackCancellationToken) =>
{
callbackCancellationToken.ThrowIfCancellationRequested();
int currentCount = Interlocked.Increment(ref asyncCount);
ScenarioConsole.WriteLine($"async interval callback #{currentCount} on registration {context.Registration.Id}");
if (currentCount >= 2)
{
asyncCompleted.TrySetResult();
}
await ValueTask.CompletedTask;
},
cancellationToken, state: "async timer", timerOptions: null);
ScenarioConsole.WriteLine("Waiting for both interval timers to fire twice...");
await Task.WhenAll(syncCompleted.Task, asyncCompleted.Task);
ScenarioConsole.WriteLine("Both interval timers completed required callback count.");
syncTimer.Dispose();
asyncTimer.Dispose();
What to notice
AddPrimeClockregistersNodaTime.IClock(defaulting toSystemClock.Instance),IPrimeClock(singletonPrimeClock), andIPrimeTime(same instance asIPrimeClock).- Both
RegisterTimer(sync) andRegisterAsyncTimer(async) accept the same(dueTime, repeat, callback, cancellationToken, state, timerOptions)shape withDurationarguments. The sharedTimeSpanoverloads remain available for cross-stack code. - Each timer registration returns an
IClockIntervalTimerthat you must dispose to stop the timer and release resources. - The callback receives an
IClockIntervalTimerCallbackContext(used here forRegistration.Idin logging) and a per-callbackCancellationTokensignalled when the timer is being disposed or the registration cancellation token fires.
Related
- Interval timer (testing) — the deterministic virtual-time counterpart for unit tests.
- Concepts: Timers, daylight saving, and testing
- API:
IPrimeClock·IClockIntervalTimer