Below is a custom UpdateablePeriodicTimer
class, which is essentially a PeriodicTimer
with a mutable Period
property:
public class UpdateablePeriodicTimer : IDisposable
{
private readonly object _locker = new();
private PeriodicTimer _timer; // Becomes null when disposed
private PeriodicTimer _newTimer;
private TimeSpan _period;
private bool _waiting;
public UpdateablePeriodicTimer(TimeSpan period)
{
_timer = new(period);
_period = period;
}
public TimeSpan Period
{
get { lock (_locker) return _period; }
set
{
PeriodicTimer timerToDispose;
lock (_locker)
{
if (_timer is null) throw new ObjectDisposedException(null,
$"The {nameof(UpdateablePeriodicTimer)} has been disposed.");
if (_waiting)
{
timerToDispose = _newTimer;
_newTimer = new(value);
}
else
{
timerToDispose = _timer;
_timer = new(value);
}
_period = value;
}
timerToDispose?.Dispose();
}
}
public async ValueTask<bool> WaitForNextTickAsync(
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ValueTask<bool> waitTask;
lock (_locker)
{
if (_timer is null) return false; // Disposed
if (_waiting) throw new InvalidOperationException();
waitTask = _timer.WaitForNextTickAsync(cancellationToken);
_waiting = true;
}
try { return await waitTask.ConfigureAwait(false); }
finally
{
PeriodicTimer timerToDispose = null;
lock (_locker)
{
_waiting = false;
if (_timer is not null && _newTimer is not null)
{
timerToDispose = _timer;
_timer = _newTimer;
_newTimer = null;
}
}
timerToDispose?.Dispose();
}
}
public void Dispose()
{
PeriodicTimer timerToDispose;
lock (_locker)
{
if (_timer is null) return; // Disposed
timerToDispose = _timer;
_timer = null;
_newTimer?.Dispose();
}
timerToDispose.Dispose();
}
}
Changing the Period
starts immediately a new PeriodicTimer
, which is swapped with the current PeriodicTimer
when the active WaitForNextTickAsync
completes, or immediately if there is no active operation.
Online demo.
The UpdateablePeriodicTimer
class is thread-safe.
A GitHub proposal for adding the Period
property in the native PeriodicTimer
, has been approved, and the implementation has been merged. So most likely it will be included in .NET 8. Changing the Period
of the native PeriodicTimer
will have different behavior than the UpdateablePeriodicTimer
implementation above. Changing the PeriodicTimer.Period
will reschedule the currently active WaitForNextTickAsync
according to the new period. On the contrary changing the UpdateablePeriodicTimer.Period
above does not affect the currently active WaitForNextTickAsync
.