diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/PeriodicTimer.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/PeriodicTimer.cs
index 7de0daf62cc6c6..6df8877031a3ee 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/PeriodicTimer.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/PeriodicTimer.cs
@@ -22,19 +22,57 @@ public sealed class PeriodicTimer : IDisposable
private readonly State _state;
/// Initializes the timer.
- /// The time interval between invocations of callback..
+ /// The period between ticks
/// must represent a number of milliseconds equal to or larger than 1, and smaller than .
public PeriodicTimer(TimeSpan period)
{
- long ms = (long)period.TotalMilliseconds;
- if (ms < 1 || ms > Timer.MaxSupportedTimeout)
+ if (!TryGetMilliseconds(period, out uint ms))
{
GC.SuppressFinalize(this);
throw new ArgumentOutOfRangeException(nameof(period));
}
_state = new State();
- _timer = new TimerQueueTimer(s => ((State)s!).Signal(), _state, (uint)ms, (uint)ms, flowExecutionContext: false);
+ _timer = new TimerQueueTimer(s => ((State)s!).Signal(), _state, ms, ms, flowExecutionContext: false);
+ }
+
+ /// Gets or sets the period between ticks.
+ /// must represent a number of milliseconds equal to or larger than 1, and smaller than .
+ ///
+ /// All prior ticks of the timer, including any that may be waiting to be consumed by ,
+ /// are unaffected by changes to . Setting affects only and all subsequent times
+ /// at which the timer will tick.
+ ///
+ public TimeSpan Period
+ {
+ get => TimeSpan.FromMilliseconds(_timer._period);
+ set
+ {
+ if (!TryGetMilliseconds(value, out uint ms))
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ _timer.Change(ms, ms);
+ }
+ }
+
+ /// Tries to extract the number of milliseconds from .
+ ///
+ /// true if the number of milliseconds is extracted and stored into ;
+ /// false if the number of milliseconds would be out of range of a timer.
+ ///
+ private static bool TryGetMilliseconds(TimeSpan value, out uint milliseconds)
+ {
+ long ms = (long)value.TotalMilliseconds;
+ if (ms >= 1 && ms <= Timer.MaxSupportedTimeout)
+ {
+ milliseconds = (uint)ms;
+ return true;
+ }
+
+ milliseconds = 0;
+ return false;
}
/// Wait for the next tick of the timer, or for the timer to be stopped.
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index 2de21393bbc2ba..764d0120d93199 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -14476,6 +14476,7 @@ public sealed partial class PeriodicTimer : System.IDisposable
public PeriodicTimer(System.TimeSpan period) { }
public void Dispose() { }
~PeriodicTimer() { }
+ public System.TimeSpan Period { get { throw null; } set { } }
public System.Threading.Tasks.ValueTask WaitForNextTickAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public static partial class Timeout
diff --git a/src/libraries/System.Runtime/tests/System/Threading/PeriodicTimerTests.cs b/src/libraries/System.Runtime/tests/System/Threading/PeriodicTimerTests.cs
index 05cdd8e9222286..f96b0b1e01fac9 100644
--- a/src/libraries/System.Runtime/tests/System/Threading/PeriodicTimerTests.cs
+++ b/src/libraries/System.Runtime/tests/System/Threading/PeriodicTimerTests.cs
@@ -25,6 +25,43 @@ public void Ctor_ValidArguments_Succeeds(uint milliseconds)
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(milliseconds));
}
+ [Fact]
+ public void Period_InvalidArguments_Throws()
+ {
+ PeriodicTimer timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1));
+ AssertExtensions.Throws("value", () => timer.Period = TimeSpan.FromMilliseconds(-1));
+ AssertExtensions.Throws("value", () => timer.Period = TimeSpan.Zero);
+ AssertExtensions.Throws("value", () => timer.Period = TimeSpan.FromMilliseconds(uint.MaxValue));
+
+ timer.Dispose();
+ Assert.Throws(() => timer.Period = TimeSpan.FromMilliseconds(100));
+ }
+
+ [Fact]
+ public void Period_Roundtrips()
+ {
+ using PeriodicTimer timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1));
+ Assert.Equal(TimeSpan.FromMilliseconds(1), timer.Period);
+
+ timer.Period = TimeSpan.FromDays(1);
+ Assert.Equal(TimeSpan.FromDays(1), timer.Period);
+
+ AssertExtensions.Throws("value", () => timer.Period = TimeSpan.Zero);
+ Assert.Equal(TimeSpan.FromDays(1), timer.Period);
+ }
+
+ [Fact]
+ public async void Period_AffectsPendingWaits()
+ {
+ using PeriodicTimer timer = new PeriodicTimer(TimeSpan.FromDays(40));
+
+ ValueTask task = timer.WaitForNextTickAsync();
+ Assert.False(task.IsCompleted);
+
+ timer.Period = TimeSpan.FromMilliseconds(1);
+ await task;
+ }
+
[Fact]
public async Task Dispose_Idempotent()
{