Skip to content

Commit b8a4f02

Browse files
authored
Add PeriodicTimer.Period property (#82560)
1 parent 37750b0 commit b8a4f02

File tree

3 files changed

+80
-4
lines changed

3 files changed

+80
-4
lines changed

src/libraries/System.Private.CoreLib/src/System/Threading/PeriodicTimer.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,57 @@ public sealed class PeriodicTimer : IDisposable
2222
private readonly State _state;
2323

2424
/// <summary>Initializes the timer.</summary>
25-
/// <param name="period">The time interval between invocations of callback..</param>
25+
/// <param name="period">The period between ticks</param>
2626
/// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> must represent a number of milliseconds equal to or larger than 1, and smaller than <see cref="uint.MaxValue"/>.</exception>
2727
public PeriodicTimer(TimeSpan period)
2828
{
29-
long ms = (long)period.TotalMilliseconds;
30-
if (ms < 1 || ms > Timer.MaxSupportedTimeout)
29+
if (!TryGetMilliseconds(period, out uint ms))
3130
{
3231
GC.SuppressFinalize(this);
3332
throw new ArgumentOutOfRangeException(nameof(period));
3433
}
3534

3635
_state = new State();
37-
_timer = new TimerQueueTimer(s => ((State)s!).Signal(), _state, (uint)ms, (uint)ms, flowExecutionContext: false);
36+
_timer = new TimerQueueTimer(s => ((State)s!).Signal(), _state, ms, ms, flowExecutionContext: false);
37+
}
38+
39+
/// <summary>Gets or sets the period between ticks.</summary>
40+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> must represent a number of milliseconds equal to or larger than 1, and smaller than <see cref="uint.MaxValue"/>.</exception>
41+
/// <remarks>
42+
/// All prior ticks of the timer, including any that may be waiting to be consumed by <see cref="WaitForNextTickAsync"/>,
43+
/// are unaffected by changes to <see cref="Period"/>. Setting <see cref="Period"/> affects only and all subsequent times
44+
/// at which the timer will tick.
45+
/// </remarks>
46+
public TimeSpan Period
47+
{
48+
get => TimeSpan.FromMilliseconds(_timer._period);
49+
set
50+
{
51+
if (!TryGetMilliseconds(value, out uint ms))
52+
{
53+
throw new ArgumentOutOfRangeException(nameof(value));
54+
}
55+
56+
_timer.Change(ms, ms);
57+
}
58+
}
59+
60+
/// <summary>Tries to extract the number of milliseconds from <paramref name="value"/>.</summary>
61+
/// <returns>
62+
/// true if the number of milliseconds is extracted and stored into <paramref name="milliseconds"/>;
63+
/// false if the number of milliseconds would be out of range of a timer.
64+
/// </returns>
65+
private static bool TryGetMilliseconds(TimeSpan value, out uint milliseconds)
66+
{
67+
long ms = (long)value.TotalMilliseconds;
68+
if (ms >= 1 && ms <= Timer.MaxSupportedTimeout)
69+
{
70+
milliseconds = (uint)ms;
71+
return true;
72+
}
73+
74+
milliseconds = 0;
75+
return false;
3876
}
3977

4078
/// <summary>Wait for the next tick of the timer, or for the timer to be stopped.</summary>

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14485,6 +14485,7 @@ public sealed partial class PeriodicTimer : System.IDisposable
1448514485
public PeriodicTimer(System.TimeSpan period) { }
1448614486
public void Dispose() { }
1448714487
~PeriodicTimer() { }
14488+
public System.TimeSpan Period { get { throw null; } set { } }
1448814489
public System.Threading.Tasks.ValueTask<bool> WaitForNextTickAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
1448914490
}
1449014491
public static partial class Timeout

src/libraries/System.Runtime/tests/System/Threading/PeriodicTimerTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,43 @@ public void Ctor_ValidArguments_Succeeds(uint milliseconds)
2525
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(milliseconds));
2626
}
2727

28+
[Fact]
29+
public void Period_InvalidArguments_Throws()
30+
{
31+
PeriodicTimer timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1));
32+
AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => timer.Period = TimeSpan.FromMilliseconds(-1));
33+
AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => timer.Period = TimeSpan.Zero);
34+
AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => timer.Period = TimeSpan.FromMilliseconds(uint.MaxValue));
35+
36+
timer.Dispose();
37+
Assert.Throws<ObjectDisposedException>(() => timer.Period = TimeSpan.FromMilliseconds(100));
38+
}
39+
40+
[Fact]
41+
public void Period_Roundtrips()
42+
{
43+
using PeriodicTimer timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1));
44+
Assert.Equal(TimeSpan.FromMilliseconds(1), timer.Period);
45+
46+
timer.Period = TimeSpan.FromDays(1);
47+
Assert.Equal(TimeSpan.FromDays(1), timer.Period);
48+
49+
AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => timer.Period = TimeSpan.Zero);
50+
Assert.Equal(TimeSpan.FromDays(1), timer.Period);
51+
}
52+
53+
[Fact]
54+
public async void Period_AffectsPendingWaits()
55+
{
56+
using PeriodicTimer timer = new PeriodicTimer(TimeSpan.FromDays(40));
57+
58+
ValueTask<bool> task = timer.WaitForNextTickAsync();
59+
Assert.False(task.IsCompleted);
60+
61+
timer.Period = TimeSpan.FromMilliseconds(1);
62+
await task;
63+
}
64+
2865
[Fact]
2966
public async Task Dispose_Idempotent()
3067
{

0 commit comments

Comments
 (0)