Dedicated Timer (HighResolutionTimerService)
A single periodic callback on its own thread, isolated from every other timer in the engine.
Status: โ Implemented ยท Visibility: Internal ยท Category: High-Resolution Timers
๐ฏ What it solves
Some periodic handlers can't tolerate sharing a thread: if a slow callback elsewhere in the process delays the tick, a safety-critical check (a heartbeat, a hard real-time gate) is delayed with it. HighResolutionTimerService dedicates a full thread and the engine's calibrated three-phase wait loop to exactly one callback at a fixed interval, so its timing is never a function of what else is registered elsewhere.
โ๏ธ How it works (in brief)
Constructing a HighResolutionTimerService does not start it โ the thread starts on an explicit Start() call. From then on, the timer maintains a metronome anchor (nextTick += intervalTicks after every fire, never recomputed from "now") and invokes the single callback once per tick with the scheduled and actual Stopwatch timestamps. If the thread falls behind by more than one interval, it skips forward rather than bursting through the backlog, and counts the skipped ticks as missed. Dispose() stops the thread (joined with a 2-second timeout) and removes the timer from the resource tree.
๐ป Usage
// Engine-internal usage โ application code does not construct this directly.
var heartbeat = new HighResolutionTimerService(
name: "HeartbeatMonitor",
intervalTicks: Stopwatch.Frequency / 1000, // 1ms
callback: (scheduled, actual) => HeartbeatMonitor.CheckAlive(),
parent: resourceRegistry.TimerDedicated,
logger: loggerFactory.CreateLogger<HighResolutionTimerService>());
heartbeat.Start(); // thread starts here; already visible in the resource tree
// Inspect timing/invocation metrics directly or via the resource graph
Console.WriteLine($"Timing error: {heartbeat.MeanTimingErrorUs:F1}us, invocations: {heartbeat.InvocationCount}");
heartbeat.Dispose(); // stops the thread, removes from the resource tree
| Option | Default | Effect |
|---|---|---|
intervalTicks |
โ (required, > 0) | Period between invocations, in Stopwatch ticks; Stopwatch.Frequency / 1000 โ 1ms |
parent |
โ (required) | Resource-tree parent; dedicated timers register under registry.TimerDedicated |
โ ๏ธ Guarantees & limits
- Full thread isolation โ no other timer or callback can delay this one's tick.
internal sealedengine type โ used by subsystems that own their own dedicated timer (e.g. a heartbeat monitor), not constructible from application code.- Callback budget is a soft contract (target < 100ยตs); a slow callback delays only this timer's own subsequent ticks, never another timer's.
- Exceptions thrown by the callback are caught, logged, and do not stop the timer.
- 1ms intervals keep one logical core near-fully busy (Yield+Spin, no room for the Sleep phase); 5ms+ intervals let Sleep absorb most of the wait, dropping CPU to near zero.
- One OS thread (default 1MB stack) per instance โ appropriate for a handful of isolated timers, not for dozens of lightweight periodic tasks (use the shared timer for those).
MeanTimingErrorUs/MaxTimingErrorUs/InvocationCount/MissedTicksare available both as direct properties and throughIMetricSourceresource-graph snapshots.
๐งช Tests
- HighResolutionTimerServiceTests โ fire-rate accuracy, callback timestamp delivery, exception-in-callback doesn't kill the timer, per-tick/callback metrics.
- HighResolutionTimerServiceBaseTests โ
Start()/Dispose()thread lifecycle, calibration, invalid-interval/null-callback validation.
๐ Related
- Parent feature: High-Resolution Timers
- Sibling: Shared Timer