Interval timer (production) — System Clock stack
This example demonstrates registering both synchronous and asynchronous interval timer callbacks on IPrimeClock using the System Clock package (KZDev.SystemClock.PrimeTime). The timer signatures use TimeSpan for due time and repeat interval — the BCL convention this stack standardizes on.
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>();
TimeSpan dueTime = _runMode == DemoRunMode.Long
? TimeSpan.FromSeconds(1)
: TimeSpan.FromMilliseconds(300);
TimeSpan repeat = _runMode == DemoRunMode.Long
? TimeSpan.FromSeconds(2)
: TimeSpan.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
AddPrimeClockregistersTimeProvider.System,IPrimeClock(singletonPrimeClock), andIPrimeTime(same instance asIPrimeClock).- Both
RegisterTimer(sync) andRegisterAsyncTimer(async) accept the same(dueTime, repeat, callback, cancellationToken, state, timerOptions)shape. - 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