1
1
using System . Diagnostics ;
2
2
using System . Globalization ;
3
- using System . Runtime . CompilerServices ;
4
3
5
4
namespace TimeProviderExtensions ;
6
5
@@ -11,13 +10,13 @@ namespace TimeProviderExtensions;
11
10
/// Learn more at <see href="https://github.com/egil/TimeProviderExtensions"/>.
12
11
/// </remarks>
13
12
[ DebuggerDisplay ( "{ToString()}. Scheduled callback count: {ScheduledCallbackCount}" ) ]
14
- public class ManualTimeProvider : TimeProvider
13
+ public partial class ManualTimeProvider : TimeProvider
15
14
{
16
15
internal const uint MaxSupportedTimeout = 0xfffffffe ;
17
16
internal const uint UnsignedInfinite = unchecked ( ( uint ) - 1 ) ;
18
17
internal static readonly DateTimeOffset DefaultStartDateTime = new ( 2000 , 1 , 1 , 0 , 0 , 0 , 0 , TimeSpan . Zero ) ;
19
18
20
- private readonly List < ManualTimerScheduledCallback > callbacks = new ( ) ;
19
+ private readonly List < ManualTimerScheduler > callbacks = new ( ) ;
21
20
private DateTimeOffset utcNow ;
22
21
private TimeZoneInfo localTimeZone ;
23
22
private TimeSpan autoAdvanceAmount = TimeSpan . Zero ;
@@ -227,7 +226,7 @@ public void SetUtcNow(DateTimeOffset value)
227
226
{
228
227
if ( value < utcNow )
229
228
{
230
- throw new ArgumentOutOfRangeException ( nameof ( value ) , $ "The new UtcNow must be greater than or equal to the curren time { utcNow } . Going back in time is not supported.") ;
229
+ throw new ArgumentOutOfRangeException ( nameof ( value ) , $ "The new UtcNow must be greater than or equal to the curren time { ToString ( ) } . Going back in time is not supported.") ;
231
230
}
232
231
233
232
lock ( callbacks )
@@ -238,16 +237,16 @@ public void SetUtcNow(DateTimeOffset value)
238
237
return ;
239
238
}
240
239
241
- while ( utcNow <= value && TryGetNext ( value ) is ManualTimerScheduledCallback mtsc )
240
+ while ( utcNow <= value && TryGetNext ( value ) is ManualTimerScheduler mtsc )
242
241
{
243
242
utcNow = mtsc . CallbackTime ;
244
- mtsc . Timer . TimerElapsed ( ) ;
243
+ mtsc . TimerElapsed ( ) ;
245
244
}
246
245
247
246
utcNow = value ;
248
247
}
249
248
250
- ManualTimerScheduledCallback ? TryGetNext ( DateTimeOffset targetUtcNow )
249
+ ManualTimerScheduler ? TryGetNext ( DateTimeOffset targetUtcNow )
251
250
{
252
251
if ( callbacks . Count > 0 && callbacks [ 0 ] . CallbackTime <= targetUtcNow )
253
252
{
@@ -266,174 +265,33 @@ public void SetUtcNow(DateTimeOffset value)
266
265
/// <returns>A string representing the clock's current time.</returns>
267
266
public override string ToString ( ) => utcNow . ToString ( "yyyy-MM-ddTHH:mm:ss.fff" , CultureInfo . InvariantCulture ) ;
268
267
269
- private void ScheduleCallback ( ManualTimer timer , TimeSpan waitTime )
268
+ private void ScheduleCallback ( ManualTimerScheduler scheduler , TimeSpan waitTime )
270
269
{
271
270
lock ( callbacks )
272
271
{
273
- var timerCallback = new ManualTimerScheduledCallback ( timer , utcNow + waitTime ) ;
274
- var insertPosition = callbacks . FindIndex ( x => x . CallbackTime > timerCallback . CallbackTime ) ;
272
+ scheduler . CallbackTime = utcNow + waitTime ;
273
+
274
+ var insertPosition = callbacks . FindIndex ( x => x . CallbackTime > scheduler . CallbackTime ) ;
275
275
276
276
if ( insertPosition == - 1 )
277
277
{
278
- callbacks . Add ( timerCallback ) ;
278
+ callbacks . Add ( scheduler ) ;
279
279
}
280
280
else
281
281
{
282
- callbacks . Insert ( insertPosition , timerCallback ) ;
282
+ callbacks . Insert ( insertPosition , scheduler ) ;
283
283
}
284
284
}
285
285
}
286
286
287
- private void RemoveCallback ( ManualTimer timer )
287
+ private void RemoveCallback ( ManualTimerScheduler timerCallback )
288
288
{
289
289
lock ( callbacks )
290
290
{
291
- var existingIndexOf = callbacks . FindIndex ( 0 , x => ReferenceEquals ( x . Timer , timer ) ) ;
291
+ var existingIndexOf = callbacks . FindIndex ( 0 , x => ReferenceEquals ( x , timerCallback ) ) ;
292
292
if ( existingIndexOf >= 0 )
293
- callbacks . RemoveAt ( existingIndexOf ) ;
294
- }
295
- }
296
-
297
- private readonly struct ManualTimerScheduledCallback :
298
- IEqualityComparer < ManualTimerScheduledCallback > ,
299
- IComparable < ManualTimerScheduledCallback >
300
- {
301
- public readonly ManualTimer Timer { get ; }
302
-
303
- public readonly DateTimeOffset CallbackTime { get ; }
304
-
305
- public ManualTimerScheduledCallback ( ManualTimer timer , DateTimeOffset callbackTime )
306
- {
307
- Timer = timer ;
308
- CallbackTime = callbackTime ;
309
- }
310
-
311
- public readonly bool Equals ( ManualTimerScheduledCallback x , ManualTimerScheduledCallback y )
312
- => ReferenceEquals ( x . Timer , y . Timer ) ;
313
-
314
- public readonly int GetHashCode ( ManualTimerScheduledCallback obj )
315
- => Timer . GetHashCode ( ) ;
316
-
317
- public readonly int CompareTo ( ManualTimerScheduledCallback other )
318
- => Comparer < DateTimeOffset > . Default . Compare ( CallbackTime , other . CallbackTime ) ;
319
- }
320
-
321
- private sealed class ManualTimer : ITimer
322
- {
323
- private ManualTimeProvider ? timeProvider ;
324
- private bool isDisposed ;
325
- private bool running ;
326
-
327
- private TimeSpan currentDueTime ;
328
- private TimeSpan currentPeriod ;
329
- private object ? state ;
330
- private TimerCallback ? callback ;
331
-
332
- public ManualTimer ( TimerCallback callback , object ? state , ManualTimeProvider timeProvider )
333
- {
334
- this . timeProvider = timeProvider ;
335
- this . callback = callback ;
336
- this . state = state ;
337
- }
338
-
339
- public bool Change ( TimeSpan dueTime , TimeSpan period )
340
- {
341
- ValidateTimeSpanRange ( dueTime ) ;
342
- ValidateTimeSpanRange ( period ) ;
343
-
344
- if ( isDisposed || timeProvider is null )
345
- {
346
- return false ;
347
- }
348
-
349
- if ( running )
350
- {
351
- timeProvider . RemoveCallback ( this ) ;
352
- }
353
-
354
- currentDueTime = dueTime ;
355
- currentPeriod = period ;
356
-
357
- if ( currentDueTime != Timeout . InfiniteTimeSpan )
358
- {
359
- ScheduleCallback ( dueTime ) ;
360
- }
361
-
362
- return true ;
363
- }
364
-
365
- public void Dispose ( )
366
- {
367
- if ( isDisposed || timeProvider is null )
368
- {
369
- return ;
370
- }
371
-
372
- isDisposed = true ;
373
-
374
- if ( running )
375
- {
376
- timeProvider . RemoveCallback ( this ) ;
377
- }
378
-
379
- callback = null ;
380
- state = null ;
381
- timeProvider = null ;
382
- }
383
-
384
- public ValueTask DisposeAsync ( )
385
- {
386
- Dispose ( ) ;
387
- return ValueTask . CompletedTask ;
388
- }
389
-
390
- internal void TimerElapsed ( )
391
- {
392
- if ( isDisposed || timeProvider is null )
393
- {
394
- return ;
395
- }
396
-
397
- running = false ;
398
-
399
- callback ? . Invoke ( state ) ;
400
-
401
- if ( currentPeriod != Timeout . InfiniteTimeSpan && currentPeriod != TimeSpan . Zero )
402
293
{
403
- ScheduleCallback ( currentPeriod ) ;
404
- }
405
- }
406
-
407
- private void ScheduleCallback ( TimeSpan waitTime )
408
- {
409
- if ( isDisposed || timeProvider is null )
410
- {
411
- return ;
412
- }
413
-
414
- running = true ;
415
-
416
- if ( waitTime == TimeSpan . Zero )
417
- {
418
- TimerElapsed ( ) ;
419
- }
420
- else
421
- {
422
- timeProvider . ScheduleCallback ( this , waitTime ) ;
423
- }
424
- }
425
-
426
- private static void ValidateTimeSpanRange ( TimeSpan time , [ CallerArgumentExpression ( "time" ) ] string ? parameter = null )
427
- {
428
- long tm = ( long ) time . TotalMilliseconds ;
429
- if ( tm < - 1 )
430
- {
431
- throw new ArgumentOutOfRangeException ( parameter , $ "{ parameter } .TotalMilliseconds must be greater than -1.") ;
432
- }
433
-
434
- if ( tm > MaxSupportedTimeout )
435
- {
436
- throw new ArgumentOutOfRangeException ( parameter , $ "{ parameter } .TotalMilliseconds must be less than than { MaxSupportedTimeout } .") ;
294
+ callbacks . RemoveAt ( existingIndexOf ) ;
437
295
}
438
296
}
439
297
}
0 commit comments