-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
EDITED by @stephentoub on 9/1/2022 to update proposal
namespace System.Threading
{
public sealed class PeriodicTimer : System.IDisposable
{
public PeriodicTimer(System.TimeSpan period) { }
public System.Threading.Tasks.ValueTask<bool> WaitForNextTickAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
public void Dispose() { }
+ public TimeSpan Period { get; set; }
}
}Background and motivation
After discovering the new PeriodicTimer class that was introduced in .NET 6 I tried to use it in a very simple implementation.
If the period between ticks is a constant interval the class is fine. However, if you have a sliding window where the period between ticks might increase (such is the case with something like an exponential backoff) you are forced to move back to using Task.Delay
To provide a real-world example of this, lets say I have a remote service where a client request a temporary lease to gain access to that service. The lease contains a TTL, and it is up to the client to renew the lease before that TTL comes to pass. Upon renewal, the service returns a new TTL which may be longer or shorter depending on the clients reputation.
It isn't possible today to use PeriodicTimer to create a background task that handles that renewal logic without explicitly creating a new instance or using gotos (implicit):
// rate based on the TTL the remote service gave us.
using var timer = new PeriodicTimer(RenewalTickRate);
while (await timer.WaitForNextTickAsync(_cancellationToken)) {
var response = await client.RenewLease(...);
Console.WriteLine($"New Lease TTL: {response.Ttl}");
// no way to adjust the timer to tick based on the new ttl
}You can accomplish this with Task.Delay by updating variables outside of a while loop, and I'm sure that is a pattern you can find in thousands of C# codebases, but then you lose out on the performance benefits PeriodicTimer offers. That being said, the underlying TimerQueueTimer that an instance of PeriodicTimer uses already supports changing its state, so I hope this isn't too major of a change to consider.
API Proposal
namespace System.Threading
{
public sealed class PeriodicTimer : System.IDisposable
{
public PeriodicTimer(System.TimeSpan period) { }
public System.Threading.Tasks.ValueTask<bool> WaitForNextTickAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
public void Dispose() { }
+ public void Change(TimeSpan period);
}
}API Usage
// rate based on the TTL the remote service gave us.
using var timer = new PeriodicTimer(RenewalTickRate);
while (await timer.WaitForNextTickAsync(_cancellationToken)) {
var response = await client.RenewLease(...);
timer.Change(response.Ttl);
}Risks
None I can immediately think of.