You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
## [1.0.0-preview.1]
11
+
12
+
This release adds a dependency on [Microsoft.Bcl.TimeProvider](https://www.nuget.org/packages/Microsoft.Bcl.TimeProvider) and utilizes the types built-in to that to do much of the work.
13
+
14
+
When using the `ManualTimeProvider` during testing, be aware of these outstanding issues: https://github.com/dotnet/runtime/issues/85326
15
+
16
+
- Removed `CancelAfter` extension methods. Instead create a CancellationTokenSource via the method `TimeProvider.CreateCancellationTokenSource(TimeSpan delay)` or in .NET 8, using `new CancellationTokenSource(TimeSpan delay, TimeProvider timeProvider).
17
+
18
+
**NOTE:** If running on .NET versions earlier than .NET 8.0, there is a constraint when invoking `CancellationTokenSource.CancelAfter(TimeSpan)` on the resultant object. This action will not terminate the initial timer indicated by `delay`. However, this restriction does not apply on .NET 8.0 and later versions.
The implementations of `TimeProvider` is abstract. An instance of `TimeProvider` for production use is availalbe on the `TimeProvider.System` property,
17
+
The implementation of `TimeProvider` is abstract. An instance of `TimeProvider` for production use is available on the `TimeProvider.System` property,
18
18
and `ManualTimeProvider` can be used during testing.
19
19
20
20
During testing, you can move time forward by calling `ForwardTime(TimeSpan)` or `SetUtcNow(DateTimeOffset)` on `ManualTimeProvider`. This allows
21
21
you to write tests that run fast and predictable, even if the system under test pauses execution for
22
22
multiple minutes using e.g. `TimeProvider.Delay(TimeSpan)`, the replacement for `Task.Delay(TimeSpan)`.
23
23
24
+
## Known issues and limitations:
25
+
26
+
- When using the `ManualTimeProvider` during testing to forward time, be aware of this issue: https://github.com/dotnet/runtime/issues/85326.
27
+
- If running on .NET versions earlier than .NET 8.0, there is a constraint when invoking `CancellationTokenSource.CancelAfter(TimeSpan)` on the `CancellationTokenSource` object returned by `CreateCancellationTokenSource(TimeSpan delay)`. This action will not terminate the initial timer indicated by the `delay` argument initially passed the `CreateCancellationTokenSource` method. However, this restriction does not apply on .NET 8.0 and later versions.
28
+
- To enable controlling `PeriodicTimer` via `TimeProvider` in versions of .NET earlier than .NET 8.0, the `TimeProvider.CreatePeriodicTimer` returns a `PeriodicTimerWrapper` object instead of a `PeriodicTimer` object. The `PeriodicTimerWrapper` type is just a lightweight wrapper around the original `System.Threading.PeriodicTimer` and will behave identically to it.
29
+
24
30
## Installation
25
31
26
32
Get the latest release from https://www.nuget.org/packages/TimeProviderExtensions
27
33
28
34
## Set up in production
29
35
30
36
To use in production, pass in `TimeProvider.System` to the types that depend on `TimeProvider`.
31
-
This can be done directly, or via an IoC Container, e.g. .NETs built-in `IServiceCollection` like so:
37
+
This can be done directly or via an IoC Container, e.g. .NETs built-in `IServiceCollection` like so:
32
38
33
39
```c#
34
40
services.AddSingleton(TimeProvider.System);
35
41
```
36
42
37
43
If you do not want to register the `TimeProvider` with your IoC container, you can instead create
38
-
an additional constructor in the types that use it, which allow you to pass in a `TimeProvider`,
44
+
an additional constructor in the types that use it, which allows you to pass in a `TimeProvider`,
39
45
and in the existing constructor(s) you have, just new up `TimeProvider.System` directly. For example:
40
46
41
47
```c#
@@ -54,20 +60,20 @@ public class MyService
54
60
}
55
61
```
56
62
57
-
This allows you to explicitly pass in an`ManualTimeProvider` during testing.
63
+
This allows you to explicitly pass in a`ManualTimeProvider` during testing.
58
64
59
65
## Example - control time during tests
60
66
61
67
If a system under test (SUT) uses things like `Task.Delay`, `DateTimeOffset.UtcNow`, `Task.WaitAsync`, or `PeriodicTimer`,
62
-
it becomes hard to create tests that runs fast and predictably.
68
+
it becomes hard to create tests that run fast and predictably.
63
69
64
70
The idea is to replace the use of e.g. `Task.Delay` with an abstraction, the `TimeProvider`, that in production
65
-
is represented by the `TimeProvider.System`, that just uses the real `Task.Delay`. During testing it is now possible to
66
-
pass in `ManualTimeProvider`, that allows the test to control the progress of time, making it possible to skip ahead,
71
+
is represented by the `TimeProvider.System`, which just uses the real `Task.Delay`. During testing it is now possible to
72
+
pass in `ManualTimeProvider`, which allows the test to control the progress of time, making it possible to skip ahead,
67
73
e.g. 10 minutes, and also pause time, leading to fast and predictable tests.
68
74
69
-
As an example, lets test the "Stuff Service" below that performs a specific tasks every 10 second with an additional
70
-
1second delay. We have two versions, one that uses the standard types in .NET, and one that uses the `TimeProvider`.
75
+
As an example, let us test the "Stuff Service" below that performs specific tasks every 10 seconds with an additional
76
+
1-second delay. We have two versions, one that uses the standard types in .NET, and one that uses the `TimeProvider`.
71
77
72
78
```c#
73
79
// Version of stuff service that uses the built in DateTimeOffset, PeriodicTimer, and Task.Delay
@@ -93,7 +99,7 @@ public class StuffService
93
99
}
94
100
}
95
101
96
-
// Version of stuff service that uses the builtin TimeProvider
102
+
// Version of stuff service that uses the built-in TimeProvider
@@ -144,7 +150,7 @@ public void DoStuff_does_stuff_every_11_seconds()
144
150
}
145
151
```
146
152
147
-
This test will run in nanoseconds, and is deterministic.
153
+
This test will run in nanoseconds and is deterministic.
148
154
149
155
Compare that to the similar test below for `StuffService` that needs to wait for 11 seconds before it can safely assert that the expectation has been met.
Copy file name to clipboardExpand all lines: src/TimeProviderExtensions/System.Threading/PeriodicTimerPort.cs
+68-24Lines changed: 68 additions & 24 deletions
Original file line number
Diff line number
Diff line change
@@ -3,22 +3,38 @@
3
3
// Original code: https://github.com/dotnet/runtime/blob/0096ba52e8c86e4d712013f6330a9b8a6496a1e0/src/libraries/System.Private.CoreLib/src/System/Threading/PeriodicTimer.cs
/// <summary>All state other than the _timer, so that the rooted timer's callback doesn't indirectly root itself by referring to _timer.</summary>
30
+
privatereadonlyState_state;
31
+
32
+
/// <summary>Initializes the timer.</summary>
33
+
/// <param name="period">The period between ticks</param>
34
+
/// <param name="timeProvider">The <see cref="TimeProvider"/> used to interpret <paramref name="period"/>.</param>
35
+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="period"/> must be <see cref="Timeout.InfiniteTimeSpan"/> or represent a number of milliseconds equal to or larger than 1 and smaller than <see cref="uint.MaxValue"/>.</exception>
36
+
/// <exception cref="ArgumentNullException"><paramref name="timeProvider"/> is null</exception>
/// <summary>Wait for the next tick of the timer, or for the timer to be stopped.</summary>
79
+
/// <param name="cancellationToken">
80
+
/// A <see cref="CancellationToken"/> to use to cancel the asynchronous wait. If cancellation is requested, it affects only the single wait operation;
81
+
/// the underlying timer continues firing.
82
+
/// </param>
83
+
/// <returns>A task that will be completed due to the timer firing, <see cref="Dispose"/> being called to stop the timer, or cancellation being requested.</returns>
84
+
/// <remarks>
85
+
/// The <see cref="PeriodicTimer"/> behaves like an auto-reset event, in that multiple ticks are coalesced into a single tick if they occur between
86
+
/// calls to <see cref="WaitForNextTickAsync"/>. Similarly, a call to <see cref="Dispose"/> will void any tick not yet consumed. <see cref="WaitForNextTickAsync"/>
87
+
/// may only be used by one consumer at a time, and may be used concurrently with a single call to <see cref="Dispose"/>.
/// <summary>Stops the timer and releases associated managed resources.</summary>
93
+
/// <remarks>
94
+
/// <see cref="Dispose"/> will cause an active wait with <see cref="WaitForNextTickAsync"/> to complete with a value of false.
95
+
/// All subsequent <see cref="WaitForNextTickAsync"/> invocations will produce a value of false.
96
+
/// </remarks>
97
+
publicvoidDispose()
98
+
{
99
+
GC.SuppressFinalize(this);
100
+
_timer.Dispose();
101
+
_state.Signal(stopping:true);
58
102
}
59
103
60
104
/// <summary>Ensures that resources are freed and other cleanup operations are performed when the garbage collector reclaims the <see cref="PeriodicTimer" /> object.</summary>
61
-
~ManualPeriodicTimer()=>Dispose();
105
+
~PeriodicTimerPort()=>Dispose();
62
106
63
107
/// <summary>Core implementation for the periodic timer.</summary>
64
108
[SuppressMessage("Reliability","CA2002:Do not lock on objects with weak identity",Justification="Code copied from Microsoft.")]
@@ -84,7 +128,7 @@ private sealed class State : IValueTaskSource<bool>
84
128
/// will never tick in such a case, and for the timer's period to be changed, the user's code would need
85
129
/// some other reference to PeriodicTimer keeping it alive, anyway.
86
130
/// </remarks>
87
-
privateManualPeriodicTimer?_owner;
131
+
privatePeriodicTimerPort?_owner;
88
132
/// <summary>Core of the <see cref="IValueTaskSource{TResult}"/> implementation.</summary>
0 commit comments