Skip to content

[API Proposal]: Expose method on PeriodicTimer to adjust interval  #60384

@andrewmd5

Description

@andrewmd5

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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions