From 50d64e9185d2ee694c7a0fb8768573b39d7bc306 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 1 Nov 2026 17:36:21 +0100 Subject: [PATCH] PeriodicTimer: net10.0, dispose the timer, TotalSeconds output, behaviour tests Moves both projects to net10.0 with MSTest 4.5.2, Microsoft.NET.Test.Sdk 18.11.0 and coverlet.collector 10.1.0. StopTaskAsync now disposes the PeriodicTimer, the elapsed time prints TotalSeconds instead of the truncated Seconds component, and the prompt says 'Press any key'. Adds four FakeTimeProvider-based tests for missed-tick coalescing, the single-consumer rule, dispose and the settable Period. --- .../PeriodicTimerCSharp.csproj | 2 +- .../PeriodicTimerMethods.cs | 3 +- .../PeriodicTimerCSharp/Program.cs | 2 +- .../PeriodicTimerBehaviorTests.cs | 61 +++++++++++++++++++ .../PeriodicTimerCSharpTests.csproj | 11 ++-- 5 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharpTests/PeriodicTimerBehaviorTests.cs diff --git a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/PeriodicTimerCSharp.csproj b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/PeriodicTimerCSharp.csproj index 91b464afea..dfb40caafc 100644 --- a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/PeriodicTimerCSharp.csproj +++ b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/PeriodicTimerCSharp.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable diff --git a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/PeriodicTimerMethods.cs b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/PeriodicTimerMethods.cs index 3d5aff4106..6466bda710 100644 --- a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/PeriodicTimerMethods.cs +++ b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/PeriodicTimerMethods.cs @@ -46,7 +46,7 @@ private async Task RandomArrayTimerAsync() Console.WriteLine(item); } - Console.WriteLine($"Created {numberOfArrays++} arrays in {stopWatch.Elapsed.Seconds}s"); + Console.WriteLine($"Created {numberOfArrays++} arrays in {stopWatch.Elapsed.TotalSeconds:F1}s"); Console.WriteLine(DateTime.Now.ToString("O")); } } @@ -65,6 +65,7 @@ public async Task StopTaskAsync() _cancellationToken.Cancel(); await _task; _cancellationToken.Dispose(); + _periodicTimer.Dispose(); Console.WriteLine("The array task was canceled"); } } \ No newline at end of file diff --git a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/Program.cs b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/Program.cs index 97686b45ef..d87920a7b6 100644 --- a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/Program.cs +++ b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharp/Program.cs @@ -6,7 +6,7 @@ var timerTask = new PeriodicTimerMethods(TimeSpan.FromSeconds(1), numOfElements); timerTask.Start(); -Console.WriteLine("Press any button to terminate the task"); +Console.WriteLine("Press any key to stop the task"); Console.ReadKey(); await timerTask.StopTaskAsync(); \ No newline at end of file diff --git a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharpTests/PeriodicTimerBehaviorTests.cs b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharpTests/PeriodicTimerBehaviorTests.cs new file mode 100644 index 0000000000..1ba04d7b63 --- /dev/null +++ b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharpTests/PeriodicTimerBehaviorTests.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Time.Testing; + +namespace PeriodicTimerCSharpTests; + +[TestClass] +public class PeriodicTimerBehaviorTests +{ + [TestMethod] + public async Task GivenMissedTicks_WhenWeWaitAgain_ThenTheyCollapseIntoOne() + { + var time = new FakeTimeProvider(); + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1), time); + + time.Advance(TimeSpan.FromSeconds(3.5)); + + Assert.IsTrue(await timer.WaitForNextTickAsync()); + + var nextTick = timer.WaitForNextTickAsync(); + Assert.IsFalse(nextTick.IsCompleted); + + time.Advance(TimeSpan.FromSeconds(0.5)); + Assert.IsTrue(await nextTick); + } + + [TestMethod] + public void GivenAWaitInFlight_WhenWeWaitTwice_ThenTheSecondCallThrows() + { + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); + + _ = timer.WaitForNextTickAsync(); + + Assert.ThrowsExactly(() => timer.WaitForNextTickAsync()); + } + + [TestMethod] + public async Task GivenAWaitInFlight_WhenWeDisposeTheTimer_ThenTheWaitReturnsFalse() + { + var timer = new PeriodicTimer(TimeSpan.FromSeconds(1)); + var tick = timer.WaitForNextTickAsync(); + + timer.Dispose(); + + Assert.IsFalse(await tick); + } + + [TestMethod] + public void GivenANewPeriod_WhenTimePasses_ThenTheTimerTicksOnTheNewSchedule() + { + var time = new FakeTimeProvider(); + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1), time); + + timer.Period = TimeSpan.FromSeconds(5); + var tick = timer.WaitForNextTickAsync(); + + time.Advance(TimeSpan.FromSeconds(4)); + Assert.IsFalse(tick.IsCompleted); + + time.Advance(TimeSpan.FromSeconds(1)); + Assert.IsTrue(tick.IsCompleted); + } +} diff --git a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharpTests/PeriodicTimerCSharpTests.csproj b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharpTests/PeriodicTimerCSharpTests.csproj index d6c9824ded..ff1b110f69 100644 --- a/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharpTests/PeriodicTimerCSharpTests.csproj +++ b/threads-csharp/PeriodicTimerCSharp/PeriodicTimerCSharpTests/PeriodicTimerCSharpTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -10,10 +10,11 @@ - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive