Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions PolyShim/Net80/TimeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,18 @@ public virtual TimeSpan GetElapsedTime(long startingTimestamp, long endingTimest
public TimeSpan GetElapsedTime(long startingTimestamp) =>
GetElapsedTime(startingTimestamp, GetTimestamp());

#if !(NETSTANDARD && !NETSTANDARD1_2_OR_GREATER)
public virtual ITimer CreateTimer(
TimerCallback callback,
object? state,
TimeSpan dueTime,
TimeSpan period
) => new SystemTimeProviderTimer(dueTime, period, callback, state);
#endif

private sealed class SystemTimeProvider : TimeProvider
{
public override TimeZoneInfo LocalTimeZone => TimeZoneInfo.Local;
}

#if !(NETSTANDARD && !NETSTANDARD1_2_OR_GREATER)
private sealed class SystemTimeProviderTimer : ITimer
{
private readonly Timer _timer;
Expand Down Expand Up @@ -90,6 +87,5 @@ public void Dispose()
public ValueTask DisposeAsync() => _timer.DisposeAsync();
#endif
}
#endif
}
#endif
156 changes: 156 additions & 0 deletions PolyShim/NetCore10/Timer.cs
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);
}

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));

var cts = new CancellationTokenSource();
var token = cts.Token;
var oldCts = Interlocked.Exchange(ref _cts, cts);

try
{
oldCts?.Cancel();
}
catch (ObjectDisposedException) { }

oldCts?.Dispose();

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();
}
}

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;
_cts?.Cancel();
_cts?.Dispose();
}
}
#endif
3 changes: 0 additions & 3 deletions PolyShim/NetCore30/Timer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#if (NETCOREAPP && !NETCOREAPP3_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD && !NETSTANDARD2_1_OR_GREATER)
// Timer is not available on .NET Standard 1.0 and 1.1
#if !(NETSTANDARD && !NETSTANDARD1_2_OR_GREATER)
#nullable enable
// ReSharper disable RedundantUsingDirective
// ReSharper disable CheckNamespace
Expand All @@ -27,4 +25,3 @@ public ValueTask DisposeAsync()
}
}
#endif
#endif
5 changes: 3 additions & 2 deletions PolyShim/Signatures.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Signatures

- **Total:** 372
- **Types:** 78
- **Total:** 373
- **Types:** 79
- **Members:** 294

___
Expand Down Expand Up @@ -462,6 +462,7 @@ ___
- `TimeProvider`
- [**[class]**](https://learn.microsoft.com/dotnet/api/system.timeprovider) <sup><sub>.NET 8.0</sub></sup>
- `Timer`
- [**[class]**](https://learn.microsoft.com/dotnet/api/system.threading.timer) <sup><sub>.NET Core 1.0</sub></sup>
- [`ValueTask DisposeAsync()`](https://learn.microsoft.com/dotnet/api/system.threading.timer.disposeasync) <sup><sub>.NET Core 3.0</sub></sup>
- `TimeSpan`
- [`TimeSpan FromMilliseconds(long, long)`](https://learn.microsoft.com/dotnet/api/system.timespan.frommilliseconds#system-timespan-frommilliseconds(system-int64-system-int64)) <sup><sub>.NET 9.0</sub></sup>
Expand Down
Loading