Skip to content

Add Timer class polyfill for .NET Standard 1.0–1.1 via Task.Delay#81

Merged
Tyrrrz merged 15 commits intomasterfrom
copilot/add-timer-class-polyfill
Feb 20, 2026
Merged

Add Timer class polyfill for .NET Standard 1.0–1.1 via Task.Delay#81
Tyrrrz merged 15 commits intomasterfrom
copilot/add-timer-class-polyfill

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 19, 2026

  • Create PolyShim/NetCore10/Timer.cs - Timer class polyfill for netstandard 1.0-1.1 using Task.Delay
  • Update PolyShim/NetCore30/Timer.cs - Remove exclusion of netstandard 1.0-1.1
  • Update PolyShim/Net80/TimeProvider.cs - Remove exclusion of netstandard 1.0-1.1
  • Primary constructor (callback, state); null check in the full-arg constructor chain
  • Start called after CTS exchange and old CTS cancellation; token captured before exchange
  • oldCts?.Cancel() wrapped in try/catch for ObjectDisposedException (race with Dispose() disposing the old CTS concurrently)
  • Post-exchange _disposed recheck also wraps cts.Cancel() in try/catch for ObjectDisposedException
  • Start(CancellationToken) - pure loop-starter, returns void
  • Schedule validates args, exchanges CTS, cancels old, then starts new loop
  • Change throws (ObjectDisposedException / ArgumentOutOfRangeException)
  • period == 0 treated same as Timeout.InfiniteTimeSpan — fires once then stops
  • Callback exceptions propagate — OperationCanceledException only caught from Task.Delay
Original prompt

Create a polyfill for the Timer class on .NET Standard 1.0-1.2 by leveraging Task.Delay


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@Tyrrrz Tyrrrz added the enhancement New feature or request label Feb 19, 2026
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
@Tyrrrz
Copy link
Copy Markdown
Owner

Tyrrrz commented Feb 19, 2026

@copilot no need for tests since we can't run tests against such old targets

Copilot AI changed the title [WIP] Add polyfill for Timer class on .NET Standard 1.0-1.2 Add Timer class polyfill for .NET Standard 1.0–1.1 via Task.Delay Feb 19, 2026
Copilot AI requested a review from Tyrrrz February 19, 2026 15:56
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 19, 2026

@copilot no need for tests since we can't run tests against such old targets

Removed in 69ef4db.

@Tyrrrz Tyrrrz marked this pull request as ready for review February 19, 2026 16:04
Copilot AI review requested due to automatic review settings February 19, 2026 16:04
@Tyrrrz Tyrrrz changed the title Add Timer class polyfill for .NET Standard 1.0–1.1 via Task.Delay Add Timer class polyfill for .NET Standard 1.0–1.1 via Task.Delay Feb 19, 2026
@Tyrrrz Tyrrrz changed the title Add Timer class polyfill for .NET Standard 1.0–1.1 via Task.Delay Add Timer class polyfill for .NET Standard 1.0–1.1 via Task.Delay Feb 19, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a System.Threading.Timer polyfill intended to enable netstandard1.0–1.1 support (implemented via Task.Delay), and removes conditional exclusions so Timer/TimeProvider.CreateTimer APIs are available on those targets.

Changes:

  • Added PolyShim/NetCore10/Timer.cs implementing a Timer type for netstandard1.0–1.1.
  • Updated PolyShim/NetCore30/Timer.cs to include DisposeAsync polyfill without excluding netstandard1.0–1.1.
  • Updated PolyShim/Net80/TimeProvider.cs to expose CreateTimer / SystemTimeProviderTimer on netstandard1.0–1.1 and updated Signatures.md accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
PolyShim/NetCore10/Timer.cs Introduces a Timer polyfill for netstandard1.0–1.1 using Task.Delay.
PolyShim/NetCore30/Timer.cs Removes the netstandard1.0–1.1 exclusion so DisposeAsync extension applies there too.
PolyShim/Net80/TimeProvider.cs Removes netstandard1.0–1.1 exclusion for CreateTimer and timer wrapper implementation.
PolyShim/Signatures.md Updates signature counts and lists Timer as a polyfilled type.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +65 to +74
if (dueTime > TimeSpan.Zero)
await Task.Delay(dueTime, cts.Token);

if (cts.IsCancellationRequested)
return;

_callback(_state);

if (period == Timeout.InfiniteTimeSpan || period <= TimeSpan.Zero)
return;
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

period <= TimeSpan.Zero is treated as “no periodic callbacks”. In the BCL, period == TimeSpan.Zero is valid and schedules callbacks back-to-back, while negative values (other than -1 / Infinite) should throw. Similarly, negative dueTime values (other than Infinite) should throw rather than being treated as immediate execution.

Copilot uses AI. Check for mistakes.
Comment on lines +109 to +116
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
var cts = Interlocked.Exchange(ref _cts, new CancellationTokenSource());
cts.Cancel();
cts.Dispose();
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dispose() swaps _cts with a newly-created CancellationTokenSource and never disposes that new instance. This leaves an undisposed CTS (and potentially its wait handle) rooted by the Timer instance. Consider setting _cts to null (making the field nullable) or reusing/discarding the existing CTS without allocating a replacement.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address this please

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address this please

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fed084cDispose now cancels and disposes _cts directly without allocating a replacement.

@Tyrrrz
Copy link
Copy Markdown
Owner

Tyrrrz commented Feb 19, 2026

@copilot please address unresolved review comments

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 19, 2026

@copilot please address unresolved review comments

Addressed in 2be4f52:

  • Change now throws directly (no try/catch swallowing)
  • ValidateTimes throws ArgumentOutOfRangeException for negative non-infinite values; period == 0 is treated as valid (uses await Task.Yield() to avoid a tight spin loop)
  • Dispose uses Interlocked.Exchange to atomically capture the CTS before cancelling/disposing

Copy link
Copy Markdown
Owner

@Tyrrrz Tyrrrz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public Timer(TimerCallback callback)
: this(callback, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan) { }

private static void ValidateTimes(TimeSpan dueTime, TimeSpan period)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's only used in one place, inline it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in fed084c — the validation is now inlined directly into CreateAndStart.

Comment on lines +109 to +116
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
var cts = Interlocked.Exchange(ref _cts, new CancellationTokenSource());
cts.Cancel();
cts.Dispose();
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address this please

Comment on lines +109 to +116
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
var cts = Interlocked.Exchange(ref _cts, new CancellationTokenSource());
cts.Cancel();
cts.Dispose();
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address this please

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
…uctor

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…e race recheck

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
@Tyrrrz Tyrrrz merged commit 8d7308a into master Feb 20, 2026
5 checks passed
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (8a9074f) to head (03a32ae).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@     Coverage Diff      @@
##   master   #81   +/-   ##
============================
============================

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants