1

I'm using

using PeriodicTimer timer = new(_period);

while (!stoppingToken.IsCancellationRequested
    && await timer.WaitForNextTickAsync(stoppingToken))
{
    await DoWork();
}

Is there a way to set _period dynamically? that is, take that period of a database for example.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Angelru
  • 150
  • 1
  • 13
  • 1
    You mean change the period on instantiated timer? – Guru Stron Jan 19 '23 at 09:08
  • yes, or some way to set the time from BBDD while the service is running. Should I do it inside the while? – Angelru Jan 19 '23 at 09:27
  • 1
    I made a contrived working example of your code, https://dotnetfiddle.net/wnKhAJ – Jodrell Jan 19 '23 at 11:13
  • 1
    As a side note, checking the `stoppingToken.IsCancellationRequested` before awaiting the `timer` results in inconsistent cancellation behavior. The loop might complete either successfully or as canceled, depending on the (non-deterministic) timing of the cancellation. My suggestion is to remove the check, to make the behavior consistent. – Theodor Zoulias Jan 19 '23 at 11:19

2 Answers2

3

No, a Periodic Timer does not allow period to be changed after instantiation.

You can construct the original or a new different Periodic Timer with a value you got from elsewhere but, be sure the Dispose instances so that the required resources can be released.

If you need a variable delay, your could use Task.Delay instead.

Jodrell
  • 34,946
  • 5
  • 87
  • 124
  • using using as in my question doesn't release resources? or do I have to call dispose when I finish my job? – Angelru Jan 19 '23 at 10:35
  • 1
    @Angelru, your use of `using` is correct and will guarantee that `Dispose` is called correctly. – Jodrell Jan 19 '23 at 10:38
  • 1
    @Angelru `using` is readonly. You will need to expand it into try-finally manually and additionally call `Dispose` in the loop when recreating the timer. – Guru Stron Jan 19 '23 at 14:44
  • but when I use using, dispose is not called automatically? – Angelru Jan 19 '23 at 19:32
2

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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104