-
Notifications
You must be signed in to change notification settings - Fork 5
Add Timer class polyfill for .NET Standard 1.0–1.1 via Task.Delay
#81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+159
−9
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
5874690
Initial plan
Copilot 2e6b4b3
Add Timer class polyfill for .NET Standard 1.0-1.1 using Task.Delay
Copilot 69ef4db
Remove Timer tests (can't run against netstandard 1.0-1.1 targets)
Copilot 2be4f52
Address review feedback on Timer polyfill
Copilot 585ca1f
Fix callback exception surfacing in Timer polyfill
Copilot fed084c
Inline ValidateTimes; fix Dispose CTS leak
Copilot ac45d37
Update PolyShim/NetCore10/Timer.cs
Tyrrrz 2cc00c7
Fix period==0 behavior, Dispose/Change race, and null callback valida…
Copilot 4703f2b
Refactor Start to accept CancellationToken instead of CancellationTok…
Copilot 7f70deb
Inline CreateAndSchedule into Schedule; keep CreateCts for field init…
Copilot 6e859db
Remove CreateCts by making _cts nullable with lazy init via primary c…
Copilot 458a10a
Move Start after CTS exchange; null-check callback in full-arg constr…
Copilot 5b71532
Update PolyShim/NetCore10/Timer.cs
Tyrrrz 9a54c8b
Guard cts.Cancel() against ObjectDisposedException in Dispose/Schedul…
Copilot 03a32ae
Guard oldCts.Cancel() against ObjectDisposedException in Schedule
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| #if NETSTANDARD && !NETSTANDARD1_2_OR_GREATER | ||
| #nullable enable | ||
| // ReSharper disable RedundantUsingDirective | ||
| // ReSharper disable CheckNamespace | ||
| // ReSharper disable InconsistentNaming | ||
| // ReSharper disable PartialTypeWithSinglePart | ||
|
|
||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace System.Threading; | ||
|
|
||
| // https://learn.microsoft.com/dotnet/api/system.threading.timer | ||
| [ExcludeFromCodeCoverage] | ||
| internal sealed class Timer(TimerCallback callback, object? state) : IDisposable | ||
| { | ||
| private CancellationTokenSource? _cts; | ||
| private volatile bool _disposed; | ||
|
|
||
| public Timer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period) | ||
| : this(callback ?? throw new ArgumentNullException(nameof(callback)), state) | ||
| { | ||
| Schedule(dueTime, period); | ||
| } | ||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| public Timer(TimerCallback callback, object? state, int dueTime, int period) | ||
| : this( | ||
| callback, | ||
| state, | ||
| TimeSpan.FromMilliseconds(dueTime), | ||
| TimeSpan.FromMilliseconds(period) | ||
| ) { } | ||
|
|
||
| public Timer(TimerCallback callback, object? state, long dueTime, long period) | ||
| : this( | ||
| callback, | ||
| state, | ||
| TimeSpan.FromMilliseconds(dueTime), | ||
| TimeSpan.FromMilliseconds(period) | ||
| ) { } | ||
|
|
||
| public Timer(TimerCallback callback) | ||
| : this(callback, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan) { } | ||
|
|
||
| private static void Start( | ||
| TimerCallback callback, | ||
| object? state, | ||
| TimeSpan dueTime, | ||
| TimeSpan period, | ||
| CancellationToken cancellationToken | ||
| ) | ||
| { | ||
| if (dueTime == Timeout.InfiniteTimeSpan) | ||
| return; | ||
|
|
||
| _ = Task.Run(async () => | ||
| { | ||
| try | ||
| { | ||
| if (dueTime > TimeSpan.Zero) | ||
| await Task.Delay(dueTime, cancellationToken); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (cancellationToken.IsCancellationRequested) | ||
| return; | ||
|
|
||
| callback(state); | ||
|
|
||
| if (period == Timeout.InfiniteTimeSpan || period == TimeSpan.Zero) | ||
| return; | ||
|
|
||
| while (!cancellationToken.IsCancellationRequested) | ||
| { | ||
| try | ||
| { | ||
| await Task.Delay(period, cancellationToken); | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (cancellationToken.IsCancellationRequested) | ||
| return; | ||
|
|
||
| callback(state); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private void Schedule(TimeSpan dueTime, TimeSpan period) | ||
| { | ||
| if (_disposed) | ||
| throw new ObjectDisposedException(nameof(Timer)); | ||
|
|
||
| if (dueTime != Timeout.InfiniteTimeSpan && dueTime < TimeSpan.Zero) | ||
| throw new ArgumentOutOfRangeException(nameof(dueTime)); | ||
| if (period != Timeout.InfiniteTimeSpan && period < TimeSpan.Zero) | ||
| throw new ArgumentOutOfRangeException(nameof(period)); | ||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| var cts = new CancellationTokenSource(); | ||
| var token = cts.Token; | ||
| var oldCts = Interlocked.Exchange(ref _cts, cts); | ||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| try | ||
| { | ||
| oldCts?.Cancel(); | ||
| } | ||
| catch (ObjectDisposedException) { } | ||
|
|
||
| oldCts?.Dispose(); | ||
|
|
||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Start(callback, state, dueTime, period, token); | ||
|
|
||
| // Handle race where Dispose completes after the initial _disposed check | ||
| // but before/just after the exchange: ensure the newly created CTS | ||
| // is also cancelled and disposed so it doesn't leak or keep firing. | ||
| if (_disposed) | ||
| { | ||
| try | ||
| { | ||
| cts.Cancel(); | ||
| } | ||
| catch (ObjectDisposedException) { } | ||
|
|
||
| cts.Dispose(); | ||
| } | ||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public bool Change(TimeSpan dueTime, TimeSpan period) | ||
| { | ||
| Schedule(dueTime, period); | ||
| return true; | ||
| } | ||
|
|
||
| public bool Change(int dueTime, int period) => | ||
| Change(TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(period)); | ||
|
|
||
| public bool Change(long dueTime, long period) => | ||
| Change(TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(period)); | ||
|
|
||
| public void Dispose() | ||
| { | ||
| if (_disposed) | ||
| return; | ||
|
|
||
| _disposed = true; | ||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _cts?.Cancel(); | ||
| _cts?.Dispose(); | ||
| } | ||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| #endif | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.