Class ObservableTimeProvider
- Namespace
- TimeAssertions
- Assembly
- TimeAssertions.dll
A TimeProvider decorator that tracks the ITimer instances created
against it so tests can assert on timer-disposal behaviour ("did this hosted service dispose
every timer it started?"). Every non-timer member delegates verbatim to the wrapped inner
provider, so it composes with any provider, most usefully a FakeTimeProvider: advance
fake time on the inner provider and assert on the active-timer set exposed here.
public sealed class ObservableTimeProvider : TimeProvider
- Inheritance
-
ObservableTimeProvider
- Inherited Members
- Extension Methods
Remarks
This fills the gap that FakeTimeProvider leaves open. The BCL fake does not surface the
timers created against it (https://github.com/dotnet/extensions/issues/7515), so
timer leaks in BackgroundService / IHostedService code cannot otherwise be
asserted without reflection. This wrapper observes
CreateTimer(TimerCallback, object?, TimeSpan, TimeSpan), callback fires (via
TimerFireCount), and timer disposal directly, staying allocation-light and
reflection-free. If the upstream proposal lands, the implementation can switch to the BCL API
behind the same assertion surface with no consumer change.
All members are thread-safe: production code under test typically creates and disposes timers from background threads while the assertion reads the active set from the test thread.
Constructors
ObservableTimeProvider(TimeProvider)
Initializes a new ObservableTimeProvider that delegates every non-timer
operation to inner and intercepts timer creation for leak tracking.
public ObservableTimeProvider(TimeProvider inner)
Parameters
innerTimeProviderThe provider to wrap. Typically a
FakeTimeProviderin tests.
Exceptions
- ArgumentNullException
inneris null.
Properties
ActiveTimerCount
Gets the number of timers created through this provider that have not yet been disposed.
public int ActiveTimerCount { get; }
Property Value
ActiveTimers
Gets a point-in-time snapshot of the still-active timers, each described by the schedule it currently carries. The returned list is a copy taken under the internal lock; later creations or disposals do not mutate it. Order is unspecified; the failure-message renderer sorts deterministically.
public IReadOnlyList<ActiveTimerInfo> ActiveTimers { get; }
Property Value
LocalTimeZone
Gets a TimeZoneInfo object that represents the local time zone according to this TimeProvider's notion of time.
public override TimeZoneInfo LocalTimeZone { get; }
Property Value
NextTimerDueTime
Gets the due time of the next pending timer: the smallest DueTime among the still-active timers that are currently enabled, or null when no enabled timer is pending. A timer whose due time is InfiniteTimeSpan is disabled (its callback will not fire until a Change(TimeSpan, TimeSpan) re-arms it) and is excluded from the calculation.
public TimeSpan? NextTimerDueTime { get; }
Property Value
Remarks
This reads the schedule the timer carries without advancing the clock, so a test can assert which delay a loop just scheduled (for example the next step of an exponential backoff) rather than advancing fake time and inferring the delay from when the callback fires. The due time is the value supplied at CreateTimer(TimerCallback, object, TimeSpan, TimeSpan), the most recent Change(TimeSpan, TimeSpan), or the timer's period once it has fired (a periodic timer re-arms at its period; a one-shot becomes disabled); it is relative to the moment that schedule was set, not a remaining countdown.
The value is computed under the internal lock, so it is a consistent snapshot even while background code creates, changes, or disposes timers concurrently.
TimerFireCount
Gets the cumulative number of times a timer callback has fired across every timer created
through this provider, counted from construction. The count is never decremented, so it
survives a timer's disposal: a heartbeat loop that fired three times and then disposed its
timer still reports 3. With a FakeTimeProvider as the inner provider a fire
happens only when test code advances fake time past a timer's due (or period) boundary, so
this is a deterministic count of the callbacks the advances triggered, not a wall-clock race.
public long TimerFireCount { get; }
Property Value
TimestampFrequency
Gets the frequency of GetTimestamp() of high-frequency value per second.
public override long TimestampFrequency { get; }
Property Value
Methods
CreateTimer(TimerCallback, object?, TimeSpan, TimeSpan)
public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
Parameters
callbackTimerCallbackA delegate representing a method to be executed when the timer fires. The method specified for callback should be reentrant, as it may be invoked simultaneously on two threads if the timer fires again before or while a previous callback is still being handled.
stateobjectAn object to be passed to the
callback. This may be null.dueTimeTimeSpanThe amount of time to delay before
callbackis invoked. Specify InfiniteTimeSpan to prevent the timer from starting. Specify Zero to start the timer immediately.periodTimeSpanThe time interval between invocations of
callback. Specify InfiniteTimeSpan to disable periodic signaling.
Returns
Exceptions
- ArgumentNullException
callbackis null.- ArgumentOutOfRangeException
The number of milliseconds in the value of
dueTimeorperiodis negative and not equal to Infinite, or is greater than MaxValue.
GetTimestamp()
Gets the current high-frequency value designed to measure small time intervals with high accuracy in the timer mechanism.
public override long GetTimestamp()
Returns
- long
A long integer representing the high-frequency counter value of the underlying timer mechanism.
GetUtcNow()
Gets a DateTimeOffset value whose date and time are set to the current Coordinated Universal Time (UTC) date and time and whose offset is Zero, all according to this TimeProvider's notion of time.
public override DateTimeOffset GetUtcNow()