< Summary

Information
Class: AmbientServices.AmbientEventTimer
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/ClockHelpers.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 88
Uncovered lines: 0
Coverable lines: 88
Total lines: 1512
Line coverage: 100%
Branch coverage
100%
Covered branches: 50
Total branches: 50
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_SchedulingClock()100%22100%
RaiseElapsed(...)100%22100%
.ctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
InitializeAmbientScheduling()100%22100%
PeriodStopwatchTicks(...)100%66100%
TimeChanged(...)100%1010100%
AmbientServices.IAmbientClockScheduledCallbackSource.get_NextScheduledCallbackStopwatchTicks()100%22100%
get_AutoReset()100%22100%
set_AutoReset(...)100%22100%
get_Enabled()100%22100%
set_Enabled(...)100%66100%
get_Interval()100%11100%
set_Interval(...)100%44100%
SetupNextRaise()100%11100%
Start()100%22100%
Stop()100%22100%
add_Elapsed(...)100%22100%
remove_Elapsed(...)100%22100%
Dispose(...)100%22100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/ClockHelpers.cs

#LineLine coverage
 1using AmbientServices.Utilities;
 2using System;
 3using System.Diagnostics;
 4using System.Runtime.Versioning;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8namespace AmbientServices;
 9
 10/// <summary>
 11/// A static class that utilizes the ambient <see cref="IAmbientClock"/> service if there is one, or the system clock if
 12/// </summary>
 13/// <remarks>
 14/// <pitch>
 15/// The way code in this library and its consumers should read time: <see cref="UtcNow"/>, <see cref="Now"/>, <see cref=
 16/// </pitch>
 17/// <pledge>
 18/// With no ambient clock, every member mirrors the system clock exactly.  <see cref="Pause"/> freezes time for the curr
 19/// </pledge>
 20/// <plan>
 21/// A static facade over <c>Ambient.GetService&lt;IAmbientClock&gt;()</c>: reads use the call-context-local service and 
 22/// </plan>
 23/// <priority>
 24/// <see cref="IAmbientClock"/>
 25/// 1. Drop-in fidelity to the framework types over a cleaner API: this facade and the ambient-clock stand-ins built on 
 26/// 2. Test determinism over test speed being incidental: while paused, a skip advances the clock instantly and fires th
 27/// </priority>
 28/// </remarks>
 29public static class AmbientClock
 30{
 31    private static readonly AmbientService<IAmbientClock> _Clock = Ambient.GetService<IAmbientClock>();
 32    /// <summary>
 33    /// Gets whether or not the ambient clock is just the system clock.
 34    /// </summary>
 35    /// <remarks>
 36    /// This property is thread-safe.
 37    /// </remarks>
 38    public static bool IsSystemClock => _Clock.Local == null;
 39    /// <summary>
 40    /// Gets the number of virtual ticks elapsed.  Ticks must be measured in units of <see cref="Stopwatch.Frequency"/>.
 41    /// </summary>
 42    /// <remarks>
 43    /// This property is thread-safe.
 44    /// </remarks>
 45    public static long Ticks => _Clock.Local?.Ticks ?? Stopwatch.GetTimestamp();
 46    /// <summary>
 47    /// Gets a <see cref="TimeSpan"/> indicating the amount of virtual time that has elapsed.  Often more convenient tha
 48    /// </summary>
 49    /// <remarks>
 50    /// This property is thread-safe.
 51    /// </remarks>
 52    public static TimeSpan Elapsed => TimeSpan.FromTicks(TimeSpanUtilities.StopwatchTicksToTimeSpanTicks(_Clock.Local?.T
 53    /// <summary>
 54    /// Gets the current virtual UTC <see cref="DateTime"/>.
 55    /// </summary>
 56    /// <remarks>
 57    /// This property is thread-safe.
 58    /// </remarks>
 59    public static DateTime UtcNow => _Clock.Local?.UtcDateTime ?? DateTime.UtcNow;
 60    /// <summary>
 61    /// Gets the current virtual local <see cref="DateTime"/>.
 62    /// </summary>
 63    /// <remarks>
 64    /// This property is thread-safe.
 65    /// </remarks>
 66    public static DateTime Now => _Clock.Local?.UtcDateTime.ToLocalTime() ?? DateTime.Now;
 67    /// <summary>
 68    /// Creates an <see cref="AmbientCancellationTokenSource"/> that cancels after the specified timeout.
 69    /// </summary>
 70    /// <param name="timeout">A <see cref="TimeSpan"/> indicating how long to wait before timing out.</param>
 71    /// <returns>An <see cref="AmbientCancellationTokenSource"/> that will cancel after the specified timeout (if any).<
 72    [Obsolete("Use new AmbientCancellationTokenSource directly")]
 73    public static AmbientCancellationTokenSource CreateCancellationTokenSource(TimeSpan timeout)
 74    {
 75        return new AmbientCancellationTokenSource(timeout);
 76    }
 77    /// <summary>
 78    /// Creates an <see cref="AmbientCancellationTokenSource"/> that cancels after the specified timeout.
 79    /// </summary>
 80    /// <param name="cancellationTokenSource">An optional <see cref="CancellationTokenSource"/> from the framework to us
 81    /// <returns>An <see cref="AmbientCancellationTokenSource"/> for the specified <see cref="CancellationTokenSource"/>
 82    [Obsolete("Use new AmbientCancellationTokenSource directly")]
 83    public static AmbientCancellationTokenSource CreateCancellationTokenSource(CancellationTokenSource? cancellationToke
 84    {
 85        return new AmbientCancellationTokenSource(cancellationTokenSource);
 86    }
 87    /// <summary>
 88    /// Pauses the time within the current call context so that no time passes until the returned <see cref="IDisposable
 89    /// </summary>
 90    /// <remarks>
 91    /// The returned instance must be disposed in the same call context.  If disposed in a child call context, the first
 92    /// </remarks>
 93    public static IDisposable Pause()
 94    {
 95        return new ScopedClockPauser();
 96    }
 97    /// <summary>
 98    /// Skips a paused clock ahead the specified amount of time.
 99    /// If the clock is not paused in the current call context, nothing is done.
 100    /// This method should only ever be called from test code that controls the passage of time.
 101    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 102    /// </summary>
 103    /// <remarks>
 104    /// Note that negative times are allowed, but should only be used to test weird clock issues.
 105    /// </remarks>
 106    /// <param name="stopwatchTicks">The number of stopwatch ticks to skip ahead.</param>
 107    public static void SkipAhead(long stopwatchTicks)
 108    {
 109        if (_Clock.Override is PausedAmbientClock controllable) controllable.SkipAhead(stopwatchTicks);
 110    }
 111    /// <summary>
 112    /// Skips a paused clock ahead the specified amount of time.
 113    /// If the clock is not paused in the current call context, nothing is done.
 114    /// This method should only ever be called from test code that controls the passage of time.
 115    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 116    /// </summary>
 117    /// <remarks>
 118    /// Note that negative times are allowed, but should only be used to test weird clock issues.
 119    /// </remarks>
 120    /// <param name="skipTime">The amount of time to skip ahead.</param>
 121    public static void SkipAhead(TimeSpan skipTime)
 122    {
 123        if (_Clock.Override is PausedAmbientClock controllable) controllable.SkipAhead(TimeSpanUtilities.TimeSpanTicksTo
 124    }
 125    /// <summary>
 126    /// The ambient clock equivalent of <see cref="Thread.Sleep(int)"/>.
 127    /// If the clock is paused, skips the virtual clock forward by the specified number of milliseconds, otherwise calls
 128    /// This method should only ever be called from test code that controls the passage of time.
 129    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 130    /// </summary>
 131    /// <remarks>
 132    /// Note that negative times are allowed, but should only be used to test weird clock issues.
 133    /// </remarks>
 134    /// <param name="millisecondsToSleep">The number of milliseconds to sleep.</param>
 135    public static void ThreadSleep(int millisecondsToSleep)
 136    {
 137        if (_Clock.Override is PausedAmbientClock controllable)
 138        {
 139            controllable.SkipAhead(millisecondsToSleep * Stopwatch.Frequency / 1000);
 140        }
 141        else
 142        {
 143            Thread.Sleep(millisecondsToSleep);
 144        }
 145    }
 146    /// <summary>
 147    /// The ambient clock equivalent of <see cref="Thread.Sleep(TimeSpan)"/>.
 148    /// Skips a paused clock ahead the specified amount of time.
 149    /// If the clock is paused, skips the virtual clock forward by the specified amount, otherwise calls <see cref="Thre
 150    /// This method should only ever be called from test code that controls the passage of time.
 151    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 152    /// </summary>
 153    /// <remarks>
 154    /// Note that negative times are allowed, but should only be used to test weird clock issues.
 155    /// </remarks>
 156    /// <param name="skipTime">The amount of time to skip ahead.</param>
 157    public static void ThreadSleep(TimeSpan skipTime)
 158    {
 159        ThreadSleep((int)skipTime.TotalMilliseconds);
 160    }
 161    /// <summary>
 162    /// Asynchronously delays for a specified amount of time.
 163    /// This method should only ever be called from test code that controls the passage of time.
 164    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 165    /// </summary>
 166    /// <param name="millisecondsToDelay">The number of milliseconds to delay.</param>
 167    private static async ValueTask Delay(long millisecondsToDelay)
 168    {
 169        if (_Clock.Override is PausedAmbientClock controllable)
 170        {
 171            controllable.SkipAhead(millisecondsToDelay * Stopwatch.Frequency / 1000);
 172            await Task.Delay(0);
 173        }
 174        else
 175        {
 176            await Task.Delay(TimeSpan.FromMilliseconds(millisecondsToDelay));
 177        }
 178    }
 179    /// <summary>
 180    /// Asynchronously delays for a specified amount of time.
 181    /// This method should only ever be called from test code that controls the passage of time.
 182    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 183    /// </summary>
 184    /// <param name="millisecondsToDelay">The number of milliseconds to delay.</param>
 185    public static ValueTask TaskDelay(int millisecondsToDelay)
 186    {
 187        return Delay(millisecondsToDelay);
 188    }
 189    /// <summary>
 190    /// Asynchronously delays for a specified amount of time.
 191    /// This method should only ever be called from test code that controls the passage of time.
 192    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 193    /// </summary>
 194    /// <param name="delayTime">The amount of time to skip ahead.</param>
 195    public static ValueTask TaskDelay(TimeSpan delayTime)
 196    {
 197        return Delay((long)delayTime.TotalMilliseconds);
 198    }
 199    private static async ValueTask Delay(long millisecondsToDelay, CancellationToken cancel)
 200    {
 201        if (_Clock.Override is PausedAmbientClock controllable)
 202        {
 203            controllable.SkipAhead(millisecondsToDelay * Stopwatch.Frequency / 1000);
 204            await Task.Delay(0, cancel);
 205        }
 206        else
 207        {
 208            await Task.Delay(TimeSpan.FromMilliseconds(millisecondsToDelay), cancel);
 209        }
 210    }
 211    /// <summary>
 212    /// Asynchronously delays for a specified amount of time.
 213    /// This method should only ever be called from test code that controls the passage of time.
 214    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 215    /// </summary>
 216    /// <param name="millisecondsToDelay">The number of milliseconds to delay</param>
 217    /// <param name="cancel">A <see cref="CancellationToken"/> that may be used to cancel the delay.</param>
 218    public static ValueTask TaskDelay(int millisecondsToDelay, CancellationToken cancel)
 219    {
 220        return Delay(millisecondsToDelay, cancel);
 221    }
 222    /// <summary>
 223    /// Asynchronously delays for a specified amount of time.
 224    /// This method should only ever be called from test code that controls the passage of time.
 225    /// Calling it from non-test code will likely cause unexpected behavior for other code.
 226    /// </summary>
 227    /// <param name="delayTime">The amount of time to skip ahead.</param>
 228    /// <param name="cancel">A <see cref="CancellationToken"/> that may be used to cancel the delay.</param>
 229    public static ValueTask TaskDelay(TimeSpan delayTime, CancellationToken cancel)
 230    {
 231        return Delay((long)delayTime.TotalMilliseconds, cancel);
 232    }
 233
 234    private sealed class ScopedClockPauser : IDisposable
 235    {
 236        private readonly IAmbientClock? _clockToRestore;
 237
 238        internal ScopedClockPauser()
 239        {
 240            _clockToRestore = _Clock.Override;
 241            _Clock.Override = new PausedAmbientClock();
 242        }
 243
 244        #region IDisposable Support
 245        private bool _disposed; // to detect redundant calls
 246
 247        private void Dispose(bool disposing)
 248        {
 249            if (!_disposed)
 250            {
 251                if (disposing)
 252                {
 253                    _Clock.Override = _clockToRestore;
 254                }
 255                _disposed = true;
 256            }
 257        }
 258        public void Dispose()
 259        {
 260            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
 261            Dispose(true);
 262        }
 263        #endregion
 264    }
 265    /// <summary>
 266    /// An ambient clock which only moves time forward when explicitly told to do so.
 267    /// </summary>
 268    /// <remarks>
 269    /// <pitch>The controllable clock installed by <see cref="AmbientClock.Pause"/>: time stands perfectly still until t
 270    /// <pledge><see cref="IAmbientClock"/></pledge>
 271    /// <pledge>Time moves only through <see cref="SkipAhead(long)"/>, and never backwards except when explicitly asked 
 272    /// <plan>Captures <see cref="Stopwatch.GetTimestamp"/> once at construction as the frozen base time and adds an <se
 273    /// </remarks>
 274    internal class PausedAmbientClock : IAmbientClock
 275    {
 276        private readonly ConcurrentHashSet<IAmbientClockTimeChangedNotificationSink> _notificationSinks = new();
 277        private readonly long _baseStopwatchTicks;
 278        private long _elapsedStopwatchTicks;
 279
 280        /// <summary>
 281        /// Constructs an ambient clock that is paused at the current date-time and only moves time forward when explici
 282        /// </summary>
 283        public PausedAmbientClock()
 284        {
 285            _baseStopwatchTicks = Stopwatch.GetTimestamp();
 286            // make sure that subsequent operations don't get a timestamp earlier than this
 287            Thread.MemoryBarrier();
 288        }
 289        /// <summary>
 290        /// Gets the number of ticks elapsed.  (In units of <see cref="Stopwatch.Frequency"/>).
 291        /// </summary>
 292        /// <remarks>
 293        /// This property is thread-safe.
 294        /// </remarks>
 295        public long Ticks => _baseStopwatchTicks + _elapsedStopwatchTicks;
 296        /// <summary>
 297        /// Gets the current UTC <see cref="DateTime"/>.
 298        /// </summary>
 299        /// <remarks>
 300        /// This property is thread-safe.
 301        /// </remarks>
 302        public DateTime UtcDateTime => UtcDateTimeFromStopwatchTicks(_baseStopwatchTicks + _elapsedStopwatchTicks);
 303
 304        private static DateTime UtcDateTimeFromStopwatchTicks(long stopwatchTicks)
 305        {
 306            return new DateTime(TimeSpanUtilities.StopwatchTimestampToDateTime(stopwatchTicks), DateTimeKind.Utc);
 307        }
 308
 309        /// <summary>
 310        /// Registers a time changed notification sink with this ambient clock.
 311        /// </summary>
 312        /// <param name="sink">An <see cref="IAmbientClockTimeChangedNotificationSink"/> that will receive notifications
 313        /// <returns>true if the registration was successful, false if the specified sink was already registered.</retur
 314        public bool RegisterTimeChangedNotificationSink(IAmbientClockTimeChangedNotificationSink sink)
 315        {
 316            return _notificationSinks.Add(sink);
 317        }
 318        /// <summary>
 319        /// Deregisters a time changed notification sink with this ambient clock.
 320        /// </summary>
 321        /// <param name="sink">An <see cref="IAmbientClockTimeChangedNotificationSink"/> that will receive notifications
 322        /// <returns>true if the deregistration was successful, false if the specified sink was not registered.</returns
 323        public bool DeregisterTimeChangedNotificationSink(IAmbientClockTimeChangedNotificationSink sink)
 324        {
 325            return _notificationSinks.Remove(sink);
 326        }
 327
 328        /// <summary>
 329        /// Moves the clock forward by the specified number of ticks.  Ticks are the same units as <see cref="Stopwatch"
 330        /// </summary>
 331        /// <remarks>
 332        /// Note that negative times are allowed, but should only be used to test weird clock issues.
 333        /// </remarks>
 334        /// <param name="ticks">The number of ticks to move forward.</param>
 335        /// <remarks>This function is not thread-safe and must only be called by one thread at a time.  It must not be c
 336        public void SkipAhead(long ticks)
 337        {
 338            long startStopwatchTicks = _baseStopwatchTicks + _elapsedStopwatchTicks;
 339            long targetStopwatchTicks = startStopwatchTicks + ticks;
 340            long positionStopwatchTicks = startStopwatchTicks;
 341            // walk forward through the scheduled callbacks in chronological order, stopping the clock at each one befor
 342            // each pass re-asks for the soonest deadline, so callbacks that reschedule themselves are picked up at thei
 343            for (long? nextStopwatchTicks = SoonestScheduledCallback(positionStopwatchTicks, targetStopwatchTicks); next
 344            {
 345                MoveTo(nextStopwatchTicks.Value);
 346                NotifyTimeChanged(positionStopwatchTicks, nextStopwatchTicks.Value);
 347                positionStopwatchTicks = nextStopwatchTicks.Value;
 348            }
 349            // settle at the requested time--this is the only step taken when nothing is scheduled, including when movin
 350            MoveTo(targetStopwatchTicks);
 351            NotifyTimeChanged(positionStopwatchTicks, targetStopwatchTicks);
 352        }
 353        /// <summary>
 354        /// Gets the soonest time any registered sink has a callback scheduled after <paramref name="afterStopwatchTicks
 355        /// </summary>
 356        /// <param name="afterStopwatchTicks">The time the clock currently sits at.  Callbacks due at or before this hav
 357        /// <param name="throughStopwatchTicks">The time the clock is moving to.  Callbacks due after this are not part 
 358        private long? SoonestScheduledCallback(long afterStopwatchTicks, long throughStopwatchTicks)
 359        {
 360            long? soonestStopwatchTicks = null;
 361            foreach (IAmbientClockTimeChangedNotificationSink notificationSink in _notificationSinks)
 362            {
 363                long? dueStopwatchTicks = (notificationSink as IAmbientClockScheduledCallbackSource)?.NextScheduledCallb
 364                if (dueStopwatchTicks == null || dueStopwatchTicks.Value <= afterStopwatchTicks || dueStopwatchTicks.Val
 365                if (soonestStopwatchTicks == null || dueStopwatchTicks.Value < soonestStopwatchTicks.Value) soonestStopw
 366            }
 367            return soonestStopwatchTicks;
 368        }
 369        private void MoveTo(long stopwatchTicks)
 370        {
 371            Interlocked.Exchange(ref _elapsedStopwatchTicks, stopwatchTicks - _baseStopwatchTicks);
 372        }
 373        private void NotifyTimeChanged(long oldTicks, long newTicks)
 374        {
 375            // notify any subscribers
 376            foreach (IAmbientClockTimeChangedNotificationSink notificationSink in _notificationSinks)
 377            {
 378                notificationSink.TimeChanged(this, oldTicks, newTicks, UtcDateTimeFromStopwatchTicks(oldTicks), UtcDateT
 379            }
 380        }
 381        /// <summary>
 382        /// Moves the clock forward by the specified amount of time.
 383        /// </summary>
 384        /// <remarks>
 385        /// Note that negative times are allowed, but should only be used to test weird clock issues.
 386        /// </remarks>
 387        /// <param name="time">A <see cref="TimeSpan"/> indicating how much to move forward.</param>
 388        /// <remarks>This function is not thread-safe and must only be called by one thread at a time.  It must not be c
 389        public void SkipAhead(TimeSpan time)
 390        {
 391            SkipAhead(TimeSpanUtilities.TimeSpanTicksToStopwatchTicks(time.Ticks));
 392        }
 393    }
 394}
 395/// <summary>
 396/// A helper class that implements the same methods and properties as <see cref="Stopwatch"/> but uses an ambient clock 
 397/// When an ambient clock is not available, should behave identically to <see cref="Stopwatch"/>.
 398/// </summary>
 399/// <remarks>
 400/// AmbientStopwatch measures elapsed time.  It has two states, running and paused.  It can be constructed in either sta
 401/// While running, <see cref="ElapsedTicks"/> will return successively increasing values (or equal values if it is calle
 402/// While not running, <see cref="ElapsedTicks"/> will return the same value, indicating the number of ticks that were p
 403/// AmbientStopwatch is not thread-safe, but neither is <see cref="Stopwatch"/>.
 404/// Threadsafe versions would be possible to implement but are much more complicated due to the race caused by the state
 405/// AmbientStopwatch does not support changing the clock implementation after construction.
 406/// <pitch>A <see cref="Stopwatch"/> lookalike whose elapsed time follows the ambient clock, so duration-measuring code 
 407/// <pledge>Behaves like <see cref="Stopwatch"/> under the running/paused state rules described above; the clock it obse
 408/// <plan>Stores the bound <see cref="IAmbientClock"/> (null meaning the system clock) plus two tick counters — ticks ac
 409/// </remarks>
 410public sealed class AmbientStopwatch
 411{
 412    private static readonly AmbientService<IAmbientClock> _Clock = Ambient.GetService<IAmbientClock>();
 413
 414    private readonly IAmbientClock? _clock;
 415    private long _accumulatedTicks;
 416    private long _resumeTicks;
 417
 418    /// <summary>
 419    /// Returns a newly constructed <see cref="AmbientStopwatch"/> that has been started.
 420    /// </summary>
 421#pragma warning disable CA1711  // the analyzer incorrectly thinks this method is a replacement to Start, but it is not-
 422    public static AmbientStopwatch StartNew()
 423#pragma warning restore CA1711
 424    {
 425        return new AmbientStopwatch(true);
 426    }
 427
 428    /// <summary>
 429    /// Constructs an AmbientStopwatch using the local ambient clock if there is one.
 430    /// </summary>
 431    /// <param name="run">Whether or not to start the stopwatch running.  Default is false (to match <see cref="Stopwatc
 432    public AmbientStopwatch(bool run = false)
 433        : this(_Clock.Local, run)
 434    {
 435    }
 436    /// <summary>
 437    /// Constructs an AmbientStopwatch using a specified <see cref="IAmbientClock"/>.  This overload is mainly for testi
 438    /// </summary>
 439    /// <param name="clock">The <see cref="IAmbientClock"/> to use, or null to use the system clock.</param>
 440    /// <param name="run">Whether or not the stopwatch should start in a running state (as opposed to a paused state).</
 441    public AmbientStopwatch(IAmbientClock? clock, bool run = true)
 442    {
 443        _clock = clock;
 444        _resumeTicks = Ticks;
 445        IsRunning = run;
 446    }
 447    /// <summary>
 448    /// Gets a timestamp number that may be used to determine how many ticks have elapsed between calls.
 449    /// The timestamp is retrieved from the default local ambient clock.
 450    /// </summary>
 451    /// <returns>A timestamp.</returns>
 452    public static long GetTimestamp()
 453    {
 454        return _Clock.Local?.Ticks ?? Stopwatch.GetTimestamp();
 455    }
 456    /// <summary>
 457    /// Gets the frequency of the stopwatch.
 458    /// </summary>
 459    public static long Frequency => Stopwatch.Frequency;    // on x86 linux, this is 1,000,000,000, but on windows it's 
 460    /// <summary>
 461    /// Gets whether or not the stopwatch supports high resolution.
 462    /// </summary>
 463    public static bool IsHighResolution => Stopwatch.IsHighResolution;
 464    /// <summary>
 465    /// Gets the ticks as determined by the clock, or the system clock if there is no clock.
 466    /// </summary>
 467    private long Ticks => _clock?.Ticks ?? Stopwatch.GetTimestamp();
 468    /// <summary>
 469    /// Gets the virtual number of ticks elapsed while the stopwatch was (or is) running.
 470    /// </summary>
 471    /// <remarks>
 472    /// The number of accumulated ticks can remain the same or go up on subsequent calls, but should never go down.
 473    /// Bugs in .NET implementations prior to 4.0 caused the system clock to sometimes go backwards.
 474    /// Even in .NET 4.0+, the clock can incorrectly jump forward and then freeze, but this should only happen on system
 475    /// Arithmetic wraparound is technically possible, though in practice, at least on Windows, this should not happen u
 476    /// In most cases, time spans measured in years should use <see cref="DateTime"/> instead of stopwatches.
 477    /// </remarks>
 478    public long ElapsedTicks => IsRunning ? (Ticks - _resumeTicks + _accumulatedTicks) : _accumulatedTicks;
 479    /// <summary>
 480    /// Gets a <see cref="TimeSpan"/> representing the number of ticks elapsed.  Based entirely on <see cref="ElapsedTic
 481    /// </summary>
 482    public TimeSpan Elapsed => TimeSpan.FromTicks(TimeSpanUtilities.StopwatchTicksToTimeSpanTicks(ElapsedTicks));
 483    /// <summary>
 484    /// Gets a <see cref="TimeSpan"/> representing the number of ticks elapsed.  Based entirely on <see cref="ElapsedTic
 485    /// </summary>
 486    public long ElapsedMilliseconds => (long)Elapsed.TotalMilliseconds;
 487    /// <summary>
 488    /// Gets whether or not the stopwatch is currently running.
 489    /// </summary>
 490    public bool IsRunning { get; private set; }
 491
 492    /// <summary>
 493    /// Stops the stopwatch so that it temporarily stops accumulating time.  While paused, <see cref="ElapsedTicks"/> wi
 494    /// </summary>
 495    public void Stop()
 496    {
 497        // pause--was it *not* paused before?
 498        if (IsRunning)
 499        {
 500            long ticksAccumulated = Ticks - _resumeTicks;
 501            IsRunning = false;
 502            _accumulatedTicks = ticksAccumulated;
 503        }
 504    }
 505    /// <summary>
 506    /// Starts the stopwatch so that time begins accumulating (again).
 507    /// <see cref="ElapsedTicks"/> will subsequently return increasing values (or the same value if called faster than t
 508    /// </summary>
 509    public void Start()
 510    {
 511        // start--was it *not* started before?
 512        if (!IsRunning)
 513        {
 514            long resumeTicks = Ticks;
 515            IsRunning = true;
 516            _resumeTicks = resumeTicks;
 517        }
 518    }
 519    /// <summary>
 520    /// Starts the stopwatch so that time begins accumulating (again).
 521    /// <see cref="ElapsedTicks"/> will subsequently return increasing values (or the same value if called faster than t
 522    /// </summary>
 523    public void Restart()
 524    {
 525        _accumulatedTicks = 0;
 526        _resumeTicks = Ticks;
 527        IsRunning = true;
 528    }
 529    /// <summary>
 530    /// Stops the stopwatch and resets the elapsed time to zero.
 531    /// </summary>
 532    public void Reset()
 533    {
 534        Stop();
 535        _accumulatedTicks = 0;
 536    }
 537}
 538/// <summary>
 539/// A helper class that implements the same methods and properties as <see cref="System.Timers.Timer"/> but uses an ambi
 540/// When an ambient clock is not available, should behave identically to <see cref="System.Timers.Timer"/>.
 541/// Note that whether the timer uses the system time or the ambient time is only determined at construction time.
 542/// </summary>
 543/// <remarks>
 544/// AmbientEventTimer is thread-safe.
 545/// <pitch>A drop-in <see cref="System.Timers.Timer"/> whose <see cref="Elapsed"/> event rides the ambient clock when on
 546/// <pledge><see cref="IAmbientClockTimeChangedNotificationSink"/></pledge>
 547/// <pledge>Which clock the timer observes is fixed at construction (a call-context clock such as one installed by <see 
 548/// <plan>When bound to an ambient clock, the base <see cref="System.Timers.Timer"/> stays permanently disabled and the 
 549/// </remarks>
 550public class AmbientEventTimer : System.Timers.Timer, IAmbientClockTimeChangedNotificationSink, IAmbientClockScheduledCa
 551{
 552#if NET6_0_OR_GREATER
 3553    private static readonly System.Reflection.ConstructorInfo _ElapsedEventArgsConstructor = typeof(System.Timers.Elapse
 3554        System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFl
 3555        new Type[] { typeof(DateTime) }, null)!;    // since we know the type, we know that this constructor will be fou
 556#else
 557    private static readonly System.Reflection.ConstructorInfo _ElapsedEventArgsConstructor = typeof(System.Timers.Elapse
 558        System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null,
 559        new Type[] { typeof(long) }, null)!;    // since we know the type, we know that this constructor will be found
 560#endif
 3561    private static readonly AmbientService<IAmbientClock> _Clock = Ambient.GetService<IAmbientClock>();
 562
 563    /// <summary>The clock scheduled timers should observe.  <see cref="AmbientService{T}.Local"/> already folds in any 
 3564    private static IAmbientClock? SchedulingClock => _Clock.Override ?? _Clock.Local;
 565
 566    private readonly IAmbientClock? _clock;           // if this is null, everything falls through to the base class (ie
 567    private long _periodStopwatchTicks;
 568    private long _nextRaiseStopwatchTicks;
 569    private int _autoReset;
 570    private int _enabled;
 571#pragma warning disable CS0649 // is never assigned a value
 572#pragma warning disable IDE0044 // Add readonly modifier
 573    private EventHolder _eventHolder;  // NOTE: making this readonly screws up *everything*
 574#pragma warning restore IDE0044 // Add readonly modifier
 575#pragma warning restore CS0649 // is never assigned a value
 576
 577    private struct EventHolder
 578    {
 579        public event System.Timers.ElapsedEventHandler Elapsed;
 580        public readonly void RaiseElapsed(object sender)
 581        {
 3582            DateTime now = AmbientClock.UtcNow;
 583#if NET6_0_OR_GREATER
 3584            System.Timers.ElapsedEventArgs args = (System.Timers.ElapsedEventArgs)_ElapsedEventArgsConstructor.Invoke(ne
 585#else
 586            long fileTime = now.ToFileTime();
 587            System.Timers.ElapsedEventArgs args = (System.Timers.ElapsedEventArgs)_ElapsedEventArgsConstructor.Invoke(ne
 588#endif
 3589            Elapsed?.Invoke(sender, args);
 3590        }
 591    }
 592
 593    /// <summary>
 594    /// Constructs an AmbientEventTimer using the ambient clock with a period of 100ms.
 595    /// The timer starts with <see cref="AutoReset"/> set to true and <see cref="Enabled"/> set to false.
 596    /// </summary>
 597    public AmbientEventTimer()
 2598        : this(SchedulingClock)
 599    {
 2600    }
 601    /// <summary>
 602    /// Constructs an AmbientEventTimer using the ambient clock and the specified period.
 603    /// The timer starts with <see cref="AutoReset"/> set to true and <see cref="Enabled"/> set to false.
 604    /// </summary>
 605    /// <param name="milliseconds">The number of milliseconds indicating how often the <see cref="Elapsed"/> event shoul
 606    public AmbientEventTimer(double milliseconds)
 2607        : this(SchedulingClock, milliseconds)
 608    {
 2609    }
 610    /// <summary>
 611    /// Constructs an AmbientEventTimer using the ambient clock and the specified period.
 612    /// The timer starts with <see cref="AutoReset"/> set to true and <see cref="Enabled"/> set to false.
 613    /// </summary>
 614    /// <param name="period">A <see cref="TimeSpan"/> indicating how often the <see cref="Elapsed"/> event should be rai
 615    public AmbientEventTimer(TimeSpan period)
 3616        : this(SchedulingClock, period)
 617    {
 3618    }
 619    /// <summary>
 620    /// Constructs an AmbientEventTimer that will use the specified clock to determine when to raise the <see cref="Elap
 621    /// The timer starts with <see cref="AutoReset"/> set to true and <see cref="Enabled"/> set to false.
 622    /// </summary>
 623    /// <param name="clock">The <see cref="IAmbientClock"/> to use to determine when to raise the <see cref="Elapsed"/> 
 624    public AmbientEventTimer(IAmbientClock? clock)
 2625        : base()
 626    {
 2627        _clock = clock;
 2628        InitializeAmbientScheduling();
 2629    }
 630    /// <summary>
 631    /// Constructs an AmbientEventTimer that will use the specified period and clock to determine when to raise the <see
 632    /// The timer starts with <see cref="AutoReset"/> set to true and <see cref="Enabled"/> set to false.
 633    /// </summary>
 634    /// <param name="clock">The <see cref="IAmbientClock"/> to use to determine when to raise the <see cref="Elapsed"/> 
 635    /// <param name="period">A <see cref="TimeSpan"/> indicating how often the <see cref="Elapsed"/> event should be rai
 636    public AmbientEventTimer(IAmbientClock? clock, TimeSpan period)
 3637        : this(clock, period.TotalMilliseconds)
 638    {
 3639    }
 640    /// <summary>
 641    /// Constructs an AmbientEventTimer that will use the specified period and clock to determine when to raise the <see
 642    /// </summary>
 643    /// <remarks>
 644    /// The period goes to the base <see cref="System.Timers.Timer"/> constructor before anything else looks at it, so a
 645    /// </remarks>
 646    /// <param name="clock">The <see cref="IAmbientClock"/> to use to determine when to raise the <see cref="Elapsed"/> 
 647    /// <param name="milliseconds">The number of milliseconds indicating how often the <see cref="Elapsed"/> event shoul
 648    private AmbientEventTimer(IAmbientClock? clock, double milliseconds)
 3649        : base(milliseconds)
 650    {
 3651        _clock = clock;
 3652        InitializeAmbientScheduling();
 3653    }
 654    /// <summary>
 655    /// Sets up the virtual scheduling state, using the interval the base class has already validated and stored.
 656    /// </summary>
 657    private void InitializeAmbientScheduling()
 658    {
 3659        _autoReset = 1;
 660        // no clock, so use the base system timer
 3661        if (_clock == null) return;
 662        // disable the system timer (it should be disabled anyway, but just in case)
 3663        base.Enabled = false;
 664
 3665        long nowStopwatchTicks = _clock.Ticks;
 3666        _periodStopwatchTicks = PeriodStopwatchTicks(base.Interval);
 3667        _nextRaiseStopwatchTicks = nowStopwatchTicks + _periodStopwatchTicks;
 3668        _enabled = 0;
 3669        _clock.RegisterTimeChangedNotificationSink(this);
 3670    }
 671    /// <summary>
 672    /// Converts an interval in milliseconds that <see cref="System.Timers.Timer"/> has already accepted into stopwatch 
 673    /// </summary>
 674    /// <remarks>
 675    /// <see cref="System.Timers.Timer"/> accepts some intervals that <see cref="TimeSpan"/> cannot represent (NaN, most
 676    /// </remarks>
 677    /// <param name="milliseconds">The interval in milliseconds, as validated and stored by the base class.</param>
 678    private static long PeriodStopwatchTicks(double milliseconds)
 679    {
 3680        if (double.IsNaN(milliseconds) || milliseconds <= 0.0 || milliseconds > TimeSpan.MaxValue.TotalMilliseconds) ret
 3681        return TimeSpanUtilities.TimeSpanTicksToStopwatchTicks(TimeSpan.FromMilliseconds(milliseconds).Ticks);
 682    }
 683    /// <summary>
 684    /// Receives notification that the ambient clock time was changed.
 685    /// </summary>
 686    /// <param name="clock">The <see cref="IAmbientClock"/> whose time was changed.</param>
 687    /// <param name="oldTicks">The old number of elapsed ticks.</param>
 688    /// <param name="newTicks">The new number of elapsed ticks.</param>
 689    /// <param name="oldUtcDateTime">The old UTC <see cref="DateTime"/>.</param>
 690    /// <param name="newUtcDateTime">The new UTC <see cref="DateTime"/>.</param>
 691    public void TimeChanged(IAmbientClock clock, long oldTicks, long newTicks, DateTime oldUtcDateTime, DateTime newUtcD
 692    {
 693        // there should be a clock if we get here
 694        Debug.Assert(_clock != null && !((System.Timers.Timer)this).Enabled);
 695        // loop because it's possible that we need to raise the event more than once
 696        // the handler may reschedule or disable the timer, so the scheduling state is re-read on every pass rather than
 3697        while (_enabled != 0)
 698        {
 699            // was it not time to raise before, but it is now?
 3700            long raiseStopwatchTicks = _nextRaiseStopwatchTicks;
 3701            if (raiseStopwatchTicks <= oldTicks || raiseStopwatchTicks > newTicks) break;
 3702            long periodStopwatchTicks = _periodStopwatchTicks;
 3703            bool autoReset = (_autoReset != 0);
 3704            _eventHolder.RaiseElapsed(this);
 705            // should we reset for another period?
 3706            if (!autoReset || periodStopwatchTicks == 0) break;
 707            // move to the next period, but stand down if the handler already rescheduled us, because that reschedule wi
 3708            Interlocked.CompareExchange(ref _nextRaiseStopwatchTicks, raiseStopwatchTicks + periodStopwatchTicks, raiseS
 709            // loop around again to check to see if we need to be raised again
 710        }
 3711    }
 712    /// <summary>
 713    /// Gets the time the <see cref="Elapsed"/> event is next due to be raised, or null if the timer is not enabled.
 714    /// </summary>
 3715    long? IAmbientClockScheduledCallbackSource.NextScheduledCallbackStopwatchTicks => (_enabled != 0) ? _nextRaiseStopwa
 716    /// <summary>
 717    /// Gets or sets whether or not the event resets and fires again after being raised.
 718    /// </summary>
 719    public new bool AutoReset
 720    {
 721        get
 722        {
 2723            return (_clock != null) ? (_autoReset != 0) : base.AutoReset;
 724        }
 725        set
 726        {
 2727            if (_clock != null)
 728            {
 2729                Interlocked.Exchange(ref _autoReset, value ? 1 : 0);
 730            }
 731            else
 732            {
 2733                base.AutoReset = value;
 734            }
 2735        }
 736    }
 737    /// <summary>
 738    /// Gets or sets whether or not the timer is enabled (ie. whether or not it will raise the <see cref="Elapsed"/> eve
 739    /// </summary>
 740    public new bool Enabled
 741    {
 2742        get { return (_clock != null) ? (_enabled != 0) : base.Enabled; }
 743        set
 744        {
 3745            if (_clock != null)
 746            {
 3747                int oldValue = Interlocked.Exchange(ref _enabled, value ? 1 : 0);
 748                // are we enabling and it was NOT enabled before?  set up the next raise
 3749                if (value && oldValue == 0) SetupNextRaise();
 750            }
 751            else
 752            {
 3753                base.Enabled = value;
 754            }
 3755        }
 756    }
 757    /// <summary>
 758    /// Gets or sets the interval (in milliseconds) for the timer.
 759    /// </summary>
 760    public new double Interval
 761    {
 762        // the base class holds the interval in both cases, so that it validates and reports exactly what the system tim
 2763        get { return base.Interval; }
 764        set
 765        {
 2766            base.Interval = value;
 2767            if (_clock != null)
 768            {
 2769                Interlocked.Exchange(ref _periodStopwatchTicks, PeriodStopwatchTicks(value));
 770                // are we enabled?
 2771                if (_enabled != 0) SetupNextRaise();
 772            }
 2773        }
 774    }
 775    private void SetupNextRaise()
 776    {
 777        Debug.Assert(_clock != null);
 3778        long now = _clock!.Ticks;   // this function is only called where _clock is not null
 3779        Interlocked.Exchange(ref _nextRaiseStopwatchTicks, now + _periodStopwatchTicks);
 3780    }
 781
 782    /// <summary>
 783    /// Starts the timer running so that the <see cref="Elapsed"/> event can be raised.
 784    /// </summary>
 785    public new void Start()
 786    {
 2787        if (_clock != null)
 788        {
 2789            Interlocked.Exchange(ref _enabled, 1);
 2790            SetupNextRaise();
 791        }
 792        else
 793        {
 2794            base.Start();
 795        }
 2796    }
 797
 798    /// <summary>
 799    /// Stops the timer running so that the <see cref="Elapsed"/> will not be raised until <see cref="Start"/> is called
 800    /// </summary>
 801    public new void Stop()
 802    {
 2803        if (_clock != null)
 804        {
 2805            Interlocked.Exchange(ref _enabled, 0);
 806        }
 807        else
 808        {
 2809            base.Stop();
 810        }
 2811    }
 812    /// <summary>
 813    /// An event that is raised each time the specified period elapses.
 814    /// </summary>
 815    public new event System.Timers.ElapsedEventHandler Elapsed
 816    {
 817        add
 818        {
 3819            if (_clock == null)
 820            {
 3821                base.Elapsed += value;
 822            }
 823            else
 824            {
 3825                _eventHolder.Elapsed += value;
 826            }
 3827        }
 828        remove
 829        {
 3830            if (_clock == null)
 831            {
 3832                base.Elapsed -= value;
 833            }
 834            else
 835            {
 3836                _eventHolder.Elapsed -= value;
 837            }
 3838        }
 839    }
 840    /// <summary>
 841    /// Disposes of this instance.  Call this base class when overriding.
 842    /// </summary>
 843    /// <param name="disposing">Whether or not the instance is being disposed (as opposed to finalized).</param>
 844    protected override void Dispose(bool disposing)
 845    {
 3846        if (_clock != null)
 847        {
 3848            Enabled = false;
 3849            _clock.DeregisterTimeChangedNotificationSink(this);
 850        }
 3851        base.Dispose(disposing);
 3852    }
 853}
 854/// <summary>
 855/// A helper class that implements the same methods and properties as <see cref="System.Threading.Timer"/> but uses an a
 856/// When an ambient clock is not available, should behave identically to <see cref="System.Threading.Timer"/>.
 857/// Note that whether the timer uses the system time or the ambient time is only determined at construction time.
 858/// </summary>
 859/// <remarks>
 860/// AmbientCallbackTimer is thread-safe.
 861/// <pitch>A drop-in <see cref="System.Threading.Timer"/> whose callbacks ride the ambient clock when one is registered 
 862/// <pledge><see cref="IAmbientClockTimeChangedNotificationSink"/></pledge>
 863/// <pledge>Which clock is observed is fixed at construction.  Behavior is that of <see cref="System.Threading.Timer"/> 
 864/// <plan>Holds exactly one of an <see cref="IAmbientClock"/> or a real <see cref="System.Threading.Timer"/>: the ambien
 865/// </remarks>
 866public sealed class AmbientCallbackTimer : MarshalByRefObject, IAmbientClockTimeChangedNotificationSink, IAmbientClockSc
 867#if NETCOREAPP3_1 || NET5_0_OR_GREATER
 868    IAsyncDisposable,
 869#endif
 870    IDisposable
 871{
 872    private static readonly AmbientService<IAmbientClock> _Clock = Ambient.GetService<IAmbientClock>();
 873    private const long MaxSupportedTimeoutMilliseconds = 0xFFFFFFFE;    // the same limit System.Threading.Timer enforce
 874    private static readonly ManualResetEvent _AlwaysSignaled = new(true);
 875    private static readonly object _UseTimerInstanceForStateIndicator = new();
 876    private static long _TimerCount;
 877
 878#if NETCOREAPP3_1 || NET5_0_OR_GREATER
 879    /// <summary>
 880    /// Gets the number of <see cref="AmbientCallbackTimer"/>s and <see cref="System.Threading.Timer"/> that are current
 881    /// Does not double-count <see cref="AmbientCallbackTimer"/> that pass through to a <see cref="System.Threading.Time
 882    /// </summary>
 883    public static long ActiveCount => _TimerCount + Timer.ActiveCount;
 884#endif
 885
 886    private readonly TimerCallback _callback;
 887    private readonly object? _state;
 888    private readonly IAmbientClock? _clock;           // exactly one of _clock and _timer should be null
 889    private readonly System.Threading.Timer? _timer;
 890
 891    private long _periodStopwatchTicks;
 892    private long _nextRaiseStopwatchTicks;
 893    private int _autoReset;
 894    private int _enabled;
 895    private bool _disposed; // To detect redundant calls
 896
 897    /// <summary>
 898    /// Constructs an AmbientCallbackTimer using the ambient clock.  The timer will not be set to call the callback.
 899    /// </summary>
 900    /// <param name="callback">A <see cref="TimerCallback"/> that is called when the time elapses.</param>
 901    public AmbientCallbackTimer(TimerCallback callback)
 902        : this(_Clock.Local, callback, _UseTimerInstanceForStateIndicator, Timeout.InfiniteTimeSpan, Timeout.InfiniteTim
 903    {
 904    }
 905    /// <summary>
 906    /// Constructs an AmbientCallbackTimer using the ambient clock and the specified period.
 907    /// </summary>
 908    /// <param name="callback">A <see cref="TimerCallback"/> that is called when the time elapses.</param>
 909    /// <param name="state">The state <see cref="object"/> to pass to the callback, or null if no such object is needed.
 910    /// <param name="dueTime">The number of milliseconds to delay before calling the callback.  <see cref="Timeout.Infin
 911    /// <param name="period">The number of milliseconds between callbacks.  <see cref="Timeout.Infinite"/> to disable pe
 912    public AmbientCallbackTimer(TimerCallback callback, object? state, int dueTime, int period)
 913        : this(_Clock.Local, callback, state, TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(period))
 914    {
 915    }
 916    /// <summary>
 917    /// Constructs an AmbientCallbackTimer using the ambient clock and the specified period.
 918    /// </summary>
 919    /// <param name="callback">A <see cref="TimerCallback"/> that is called when the time elapses.</param>
 920    /// <param name="state">The state <see cref="object"/> to pass to the callback.</param>
 921    /// <param name="dueTime">The number of milliseconds to delay before calling the callback.  <see cref="Timeout.Infin
 922    /// <param name="period">The number of milliseconds between callbacks.  <see cref="Timeout.Infinite"/> to disable pe
 923    [CLSCompliant(false)]
 924    public AmbientCallbackTimer(TimerCallback callback, object? state, uint dueTime, uint period)
 925        : this(_Clock.Local, callback, state, TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(period))
 926    {
 927    }
 928    /// <summary>
 929    /// Constructs an AmbientCallbackTimer using the ambient clock and the specified period.
 930    /// </summary>
 931    /// <param name="callback">A <see cref="TimerCallback"/> that is called when the time elapses.</param>
 932    /// <param name="state">The state <see cref="object"/> to pass to the callback.</param>
 933    /// <param name="dueTime">The number of milliseconds to delay before calling the callback.  <see cref="Timeout.Infin
 934    /// <param name="period">The number of milliseconds between callbacks.  <see cref="Timeout.Infinite"/> to disable pe
 935    public AmbientCallbackTimer(TimerCallback callback, object? state, long dueTime, long period)
 936        : this(_Clock.Local, callback, state, TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(period))
 937    {
 938    }
 939    /// <summary>
 940    /// Constructs an AmbientCallbackTimer using the ambient clock and the specified period.
 941    /// </summary>
 942    /// <param name="callback">A <see cref="TimerCallback"/> that is called when the time elapses.</param>
 943    /// <param name="state">The state <see cref="object"/> to pass to the callback.</param>
 944    /// <param name="dueTime">A <see cref="TimeSpan"/> indicating the number of milliseconds to delay before calling the
 945    /// <param name="period">A <see cref="TimeSpan"/> indicating the number of milliseconds between callbacks.  <see cre
 946    public AmbientCallbackTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
 947        : this(_Clock.Local, callback, state, dueTime, period)
 948    {
 949    }
 950    /// <summary>
 951    /// Constructs an AmbientCallbackTimer using the ambient clock and the specified period.
 952    /// </summary>
 953    /// <param name="clock">The <see cref="IAmbientClock"/> to use to determine when to invoke the callback.</param>
 954    /// <param name="callback">A <see cref="TimerCallback"/> that is called when the time elapses.</param>
 955    /// <param name="state">The state <see cref="object"/> to pass to the callback.</param>
 956    /// <param name="dueTime">A <see cref="TimeSpan"/> indicating the number of milliseconds to delay before calling the
 957    /// <param name="period">A <see cref="TimeSpan"/> indicating the number of milliseconds between callbacks.  <see cre
 958    public AmbientCallbackTimer(IAmbientClock? clock, TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan 
 959    {
 960        ValidateTime(dueTime, nameof(dueTime));
 961        ValidateTime(period, nameof(period));
 962
 963        _callback = callback ?? throw new ArgumentNullException(nameof(callback));
 964        _state = ReferenceEquals(state, _UseTimerInstanceForStateIndicator) ? this : state;
 965        _clock = clock;
 966        // is there a clock?
 967        if (clock != null)
 968        {
 969            _autoReset = (period == Timeout.InfiniteTimeSpan) ? 0 : 1;
 970            _enabled = (dueTime == Timeout.InfiniteTimeSpan) ? 0 : 1;
 971            if (_enabled != 0)
 972            {
 973                Enable(dueTime, period);
 974            }
 975        }
 976        else // no clock, so just fall through to using a system threading timer
 977        {
 978            _timer = new System.Threading.Timer(callback, state, dueTime, period);
 979        }
 980    }
 981
 982    // when there is an ambient clock, events are raised ONLY when the clock changes, and we get notified here every tim
 983    /// <summary>
 984    /// Receives notification that the ambient clock time was changed.
 985    /// </summary>
 986    /// <param name="clock">The <see cref="IAmbientClock"/> whose time was changed.</param>
 987    /// <param name="oldTicks">The old number of elapsed ticks.</param>
 988    /// <param name="newTicks">The new number of elapsed ticks.</param>
 989    /// <param name="oldUtcDateTime">The old UTC <see cref="DateTime"/>.</param>
 990    /// <param name="newUtcDateTime">The new UTC <see cref="DateTime"/>.</param>
 991    void IAmbientClockTimeChangedNotificationSink.TimeChanged(IAmbientClock clock, long oldTicks, long newTicks, DateTim
 992    {
 993        if (_clock == null) throw new InvalidOperationException("TimeChanged may only be used with non-system ambient cl
 994        // the callback may reschedule or disable the timer, so the scheduling state is re-read on every pass rather tha
 995        // caching it would let a callback that re-arms the timer be measured against a stale deadline, which never adva
 996        while (_callback != null && _enabled != 0)
 997        {
 998            // was it not time to raise before, but it is now?
 999            long raiseStopwatchTicks = _nextRaiseStopwatchTicks;
 1000            if (raiseStopwatchTicks <= oldTicks || raiseStopwatchTicks > newTicks) break;
 1001            long periodStopwatchTicks = _periodStopwatchTicks;
 1002            bool autoReset = (_autoReset != 0);
 1003            // should we reset for another period?
 1004            if (autoReset && periodStopwatchTicks != Timeout.Infinite)
 1005            {
 1006                Interlocked.Add(ref _nextRaiseStopwatchTicks, periodStopwatchTicks);
 1007                // we might loop around again and invoke the callback again depending on how much the time changed
 1008            }
 1009            else // we're no longer active, as the period indicates that we shouldn't invoke the callback again
 1010            {
 1011                Disable();
 1012                // _enabled getting set to zero should cause us to break out of the loop, unless the callback reenables 
 1013            }
 1014            _callback.Invoke(_state);
 1015        }
 1016    }
 1017    /// <summary>
 1018    /// Gets the time the callback is next due to be invoked, or null if the timer is not enabled.
 1019    /// </summary>
 1020    long? IAmbientClockScheduledCallbackSource.NextScheduledCallbackStopwatchTicks => (_enabled != 0) ? _nextRaiseStopwa
 1021
 1022    /// <summary>
 1023    /// Validates a due time or period against the same limits <see cref="System.Threading.Timer"/> applies.
 1024    /// </summary>
 1025    /// <param name="time">The <see cref="TimeSpan"/> to validate, with <see cref="Timeout.InfiniteTimeSpan"/> always al
 1026    /// <param name="parameterName">The name of the parameter being validated, for the exception.</param>
 1027    private static void ValidateTime(TimeSpan time, string parameterName)
 1028    {
 1029        if (time == Timeout.InfiniteTimeSpan) return;
 1030        long milliseconds = (long)time.TotalMilliseconds;
 1031        if (milliseconds < 0) throw new ArgumentOutOfRangeException(parameterName, "The parameter must not be negative u
 1032        if (milliseconds > MaxSupportedTimeoutMilliseconds) throw new ArgumentOutOfRangeException(parameterName, $"The p
 1033    }
 1034
 1035    private void Disable()
 1036    {
 1037        // this currently only gets called when there is a clock and not a timer
 1038        Debug.Assert(_clock != null && _timer == null);
 1039        // race to disable us--did we win the race?
 1040        if (1 == Interlocked.Exchange(ref _enabled, 0))
 1041        {
 1042            _clock!.DeregisterTimeChangedNotificationSink(this);
 1043            Interlocked.Decrement(ref _TimerCount);
 1044        }
 1045    }
 1046    private void Enable(TimeSpan dueTime, TimeSpan period)
 1047    {
 1048        Interlocked.Increment(ref _TimerCount);
 1049        long nowStopwatchTicks = _clock!.Ticks; // this is only called where _clock is not null
 1050#if DEBUG
 1051        IAmbientClock tempClock = _clock;
 1052#endif
 1053        _clock.RegisterTimeChangedNotificationSink(this);
 1054        _periodStopwatchTicks = TimeSpanUtilities.TimeSpanTicksToStopwatchTicks(period.Ticks);
 1055        long ticksToNextInvocation = TimeSpanUtilities.TimeSpanTicksToStopwatchTicks(dueTime.Ticks);
 1056        _nextRaiseStopwatchTicks = nowStopwatchTicks + ticksToNextInvocation;
 1057    }
 1058
 1059    /// <summary>
 1060    /// Changes the time when the timer will activate, ignoring all previous activations and timing settings.
 1061    /// </summary>
 1062    /// <param name="dueTime">The number of milliseconds before the timer will go off for the first time, with <see cref
 1063    /// <param name="period">The number of milliseconds indicating how often the timer will go off after the first activ
 1064    /// <returns>true if the timer was successfully updated, otherwise false.</returns>
 1065    public bool Change(int dueTime, int period)
 1066    {
 1067        return Change(TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(period));
 1068    }
 1069    /// <summary>
 1070    /// Changes the time when the timer will activate, ignoring all previous activations and timing settings.
 1071    /// </summary>
 1072    /// <param name="dueTime">The number of milliseconds before the timer will go off for the first time, with <see cref
 1073    /// <param name="period">The number of milliseconds indicating how often the timer will go off after the first activ
 1074    /// <returns>true if the timer was successfully updated, otherwise false.</returns>
 1075    public bool Change(long dueTime, long period)
 1076    {
 1077        return Change(TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(period));
 1078    }
 1079    /// <summary>
 1080    /// Changes the time when the timer will activate, ignoring all previous activations and timing settings.
 1081    /// </summary>
 1082    /// <param name="dueTime">The number of milliseconds before the timer will go off for the first time, with <see cref
 1083    /// <param name="period">The number of milliseconds indicating how often the timer will go off after the first activ
 1084    /// <returns>true if the timer was successfully updated, otherwise false.</returns>
 1085    [CLSCompliant(false)]
 1086    public bool Change(uint dueTime, uint period)
 1087    {
 1088        return Change(TimeSpan.FromMilliseconds(dueTime), TimeSpan.FromMilliseconds(period));
 1089    }
 1090    /// <summary>
 1091    /// Changes the time when the timer will activate, ignoring all previous activations and timing settings.
 1092    /// </summary>
 1093    /// <param name="dueTime">A <see cref="TimeSpan"/> indicating the amount of time before the timer will go off for th
 1094    /// <param name="period">A <see cref="TimeSpan"/> indicating how often the timer will go off after the first activat
 1095    /// <returns>true if the timer was successfully updated, otherwise false.</returns>
 1096    public bool Change(TimeSpan dueTime, TimeSpan period)
 1097    {
 1098        if (_clock != null)
 1099        {
 1100            ValidateTime(dueTime, nameof(dueTime));
 1101            ValidateTime(period, nameof(period));
 1102
 1103            // were we enabled before?
 1104            if (_enabled != 0)
 1105            {
 1106                Disable();
 1107            }
 1108            _autoReset = (period == Timeout.InfiniteTimeSpan) ? 0 : 1;
 1109            // race to enable us--did we win the race?
 1110            int newEnabled = (dueTime == Timeout.InfiniteTimeSpan) ? 0 : 1;
 1111            if (newEnabled != 0 && Interlocked.Exchange(ref _enabled, newEnabled) == 0)
 1112            {
 1113                Enable(dueTime, period);
 1114            }
 1115            return true;
 1116        }
 1117        else
 1118        {
 1119            return _timer!.Change(dueTime, period); // if _clock is null, _timer cannot be!
 1120        }
 1121    }
 1122
 1123#region IDisposable Support
 1124    /// <summary>
 1125    /// Disposes of the timer, signaling an optional <see cref="WaitHandle"/> when the disposal is complete (meaning tha
 1126    /// </summary>
 1127    /// <param name="waitHandle">The <see cref="WaitHandle"/> to signal when the disposal is complete, or null if no not
 1128    /// <returns>true if the disposal was successful and needed, otherwise false.</returns>
 1129    public bool Dispose(WaitHandle waitHandle)
 1130    {
 1131        bool ret = false;
 1132        if (!_disposed)
 1133        {
 1134            if (_clock != null)
 1135            {
 1136                // the system path gets this validation from the timer it forwards to, but the ambient path has to do it
 1137                if (waitHandle == null) throw new ArgumentNullException(nameof(waitHandle));
 1138                bool enabled = (_enabled != 0);
 1139                Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); // note that this disables the timer and dec
 1140                // since notification when we have an ambient clock service is synchronous, there is no need to wait for
 1141                WaitHandle.SignalAndWait(waitHandle, _AlwaysSignaled);
 1142                // return whether or not we were already canceled
 1143                ret = enabled;
 1144            }
 1145            else
 1146            {
 1147                ret = _timer!.Dispose(waitHandle);  // if _clock is null, _timer cannot be!
 1148            }
 1149            _disposed = true;
 1150        }
 1151        return ret;
 1152    }
 1153
 1154#if NETCOREAPP3_1 || NET5_0_OR_GREATER
 1155    /// <summary>
 1156    /// Asynchronously disposes the instance.
 1157    /// </summary>
 1158    /// <returns>A <see cref="ValueTask"/> allowing the caller to control and monitor the asynchronous execution.</retur
 1159    public async ValueTask DisposeAsync()
 1160    {
 1161        // Perform async cleanup.
 1162        await DisposeAsyncCore();
 1163
 1164        // Dispose of unmanaged resources.
 1165        Dispose();
 1166        // Suppress finalization.
 1167#pragma warning disable CA1816      // this is the *recommended* implementation!  see https://docs.microsoft.com/en-us/d
 1168        GC.SuppressFinalize(this);
 1169#pragma warning disable CA1816
 1170    }
 1171    private ValueTask DisposeAsyncCore()    // note that this would be protected virtual if this class were not sealed
 1172    {
 1173        if (_timer != null)
 1174        {
 1175            return _timer.DisposeAsync();
 1176        }
 1177        return default;
 1178    }
 1179#endif
 1180    /// <summary>
 1181    /// Disposes of this instance.
 1182    /// </summary>
 1183    public void Dispose()
 1184    {
 1185        if (!_disposed)
 1186        {
 1187            if (_clock != null)
 1188            {
 1189                Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
 1190            }
 1191            else
 1192            {
 1193                _timer!.Dispose();  // if _clock is null, _timer cannot be
 1194            }
 1195            Interlocked.Decrement(ref _TimerCount);
 1196            _disposed = true;
 1197        }
 1198        GC.SuppressFinalize(this);
 1199    }
 1200
 1201#endregion
 1202}
 1203/// <summary>
 1204/// A sealed class that emulates <see cref="RegisteredWaitHandle"/> but uses the ambient clock if one is registered.
 1205/// </summary>
 1206/// <remarks>
 1207/// <pitch>The ambient-clock counterpart of <see cref="RegisteredWaitHandle"/>, created through <see cref="AmbientThread
 1208/// <pledge><see cref="IAmbientClockTimeChangedNotificationSink"/></pledge>
 1209/// <pledge>Behavior is that of the corresponding <see cref="ThreadPool"/> registration with virtual time substituted fo
 1210/// <plan>Always registers with the real <see cref="ThreadPool"/> for the signal leg — with an infinite timeout when an 
 1211/// </remarks>
 1212#if NET5_0_OR_GREATER
 1213[UnsupportedOSPlatform("browser")]
 1214#endif
 1215public sealed class AmbientRegisteredWaitHandle : IAmbientClockTimeChangedNotificationSink, IAmbientClockScheduledCallba
 1216{
 1217    private static readonly AmbientService<IAmbientClock> _Clock = Ambient.GetService<IAmbientClock>();
 1218    private static readonly ManualResetEvent _ManualResetEvent = new(true);
 1219
 1220
 1221    private readonly RegisteredWaitHandle _registeredWaitHandle;
 1222    private readonly IAmbientClock? _clock;
 1223    private readonly WaitOrTimerCallback _callback;
 1224    private readonly bool _executeOnlyOnce;
 1225    private readonly object? _state;
 1226    private readonly long _periodStopwatchTicks;
 1227    private readonly ExecutionContext? _executionContext;
 1228    private long _nextCallbackTimeStopwatchTicks;
 1229
 1230    internal AmbientRegisteredWaitHandle(bool safe, WaitHandle waitHandle, WaitOrTimerCallback callback, object? state, 
 1231        : this(waitHandle, callback, state, millisecondTimeoutInterval, executeOnlyOnce, safe)
 1232    {
 1233    }
 1234    internal AmbientRegisteredWaitHandle(bool safe, WaitHandle waitHandle, WaitOrTimerCallback callback, object? state, 
 1235        : this(waitHandle, callback, state, millisecondTimeoutInterval, executeOnlyOnce, safe)
 1236    {
 1237    }
 1238    internal AmbientRegisteredWaitHandle(bool safe, WaitHandle waitHandle, WaitOrTimerCallback callback, object? state, 
 1239        : this(waitHandle, callback, state, millisecondTimeoutInterval, executeOnlyOnce, safe)
 1240    {
 1241    }
 1242    internal AmbientRegisteredWaitHandle(bool safe, WaitHandle waitHandle, WaitOrTimerCallback callback, object? state, 
 1243        : this(waitHandle, callback, state, (long)timeoutInterval.TotalMilliseconds, executeOnlyOnce, safe)
 1244    {
 1245    }
 1246#if NET5_0_OR_GREATER
 1247    [UnsupportedOSPlatform("browser")]
 1248#endif
 1249    private AmbientRegisteredWaitHandle(WaitHandle waitHandle, WaitOrTimerCallback callback, object? state, long millise
 1250    {
 1251        if ((_clock = _Clock.Local) == null)
 1252        {
 1253            _registeredWaitHandle = safe
 1254                ? ThreadPool.RegisterWaitForSingleObject(waitHandle, callback, state, millisecondTimeoutInterval, execut
 1255                : ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, callback, state, millisecondTimeoutInterval, 
 1256            _callback = (o, b) => { };
 1257        }
 1258        else
 1259        {
 1260            // the system path gets this validation from the ThreadPool registration it forwards to, but the ambient pat
 1261            if (callback == null) throw new ArgumentNullException(nameof(callback));
 1262            if (millisecondTimeoutInterval < -1) throw new ArgumentOutOfRangeException(nameof(millisecondTimeoutInterval
 1263            _callback = callback;
 1264            _state = state;
 1265            _registeredWaitHandle = safe
 1266                ? ThreadPool.RegisterWaitForSingleObject(waitHandle, OnWaitHandleSignaled, null, -1, executeOnlyOnce)
 1267                : ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, OnWaitHandleSignaled, null, -1, executeOnlyOn
 1268            if (safe)
 1269            {
 1270                _executionContext = ExecutionContext.Capture();
 1271            }
 1272            long timeoutIntervalStopwatchTicks = millisecondTimeoutInterval * Stopwatch.Frequency / 1000;
 1273            _nextCallbackTimeStopwatchTicks = (millisecondTimeoutInterval == Timeout.Infinite) ? Timeout.Infinite : (_cl
 1274            _periodStopwatchTicks = executeOnlyOnce ? Timeout.Infinite : timeoutIntervalStopwatchTicks;
 1275            _executeOnlyOnce = executeOnlyOnce;
 1276            _clock.RegisterTimeChangedNotificationSink(this);
 1277        }
 1278    }
 1279
 1280#if NET5_0_OR_GREATER
 1281    [UnsupportedOSPlatform("browser")]
 1282#endif
 1283    private void OnWaitHandleSignaled(object? state, bool timedOut)
 1284    {
 1285        Debug.Assert(_clock != null);
 1286        // only execute once?
 1287        if (_executeOnlyOnce)
 1288        {
 1289            // disable further signal invocations
 1290            _registeredWaitHandle.Unregister(_ManualResetEvent);
 1291        }
 1292        // no period (ie. this is the only callback)?
 1293        if (_periodStopwatchTicks == Timeout.Infinite)
 1294        {   // cancel all further timed callbacks
 1295            Interlocked.Exchange(ref _nextCallbackTimeStopwatchTicks, Timeout.Infinite);
 1296        }
 1297        else
 1298        {   // schedule the next callback
 1299            Interlocked.Exchange(ref _nextCallbackTimeStopwatchTicks, _clock!.Ticks + _periodStopwatchTicks);  // this f
 1300        }
 1301        // the wait handle was signaled--we should always call the callback in this case
 1302        _callback(_state, false);
 1303
 1304    }
 1305    /// <summary>
 1306    /// Receives notification that the ambient clock time was changed.
 1307    /// </summary>
 1308    /// <param name="clock">The <see cref="IAmbientClock"/> whose time was changed.</param>
 1309    /// <param name="oldTicks">The old number of elapsed ticks.</param>
 1310    /// <param name="newTicks">The new number of elapsed ticks.</param>
 1311    /// <param name="oldUtcDateTime">The old UTC <see cref="DateTime"/>.</param>
 1312    /// <param name="newUtcDateTime">The new UTC <see cref="DateTime"/>.</param>
 1313    public void TimeChanged(IAmbientClock clock, long oldTicks, long newTicks, DateTime oldUtcDateTime, DateTime newUtcD
 1314    {// when there is an ambient clock, events are raised ONLY when the clock changes, and we get notified here every ti
 1315        // there should be a clock if we get here
 1316        Debug.Assert(_clock != null);
 1317        // loop until we process all the scheduled callbacks
 1318        while (_nextCallbackTimeStopwatchTicks != Timeout.Infinite && _nextCallbackTimeStopwatchTicks > oldTicks && _nex
 1319        {
 1320            // should we reset for another period?
 1321            if (_periodStopwatchTicks != Timeout.Infinite)
 1322            {
 1323                Interlocked.Add(ref _nextCallbackTimeStopwatchTicks, _periodStopwatchTicks);
 1324                // we may loop around again in case the event should have been raised more than once
 1325            }
 1326            else
 1327            {
 1328                // this should cause the loop to stop, but only AFTER we invoke the callback
 1329                _nextCallbackTimeStopwatchTicks = Timeout.Infinite;
 1330            }
 1331            // only execute once?
 1332            if (_executeOnlyOnce)
 1333            {
 1334                // disable further signal invocations
 1335                _registeredWaitHandle.Unregister(_ManualResetEvent);
 1336            }
 1337            if (_executionContext != null)
 1338            {
 1339                // run in the execution context of the constructor
 1340                ExecutionContext.Run(_executionContext, state => _callback(state, true), _state);
 1341            }
 1342            else
 1343            {
 1344                _callback(_state, true);
 1345            }
 1346        }
 1347    }
 1348    /// <summary>
 1349    /// Gets the time the timeout callback is next due to be invoked, or null if no timed callback is scheduled.
 1350    /// </summary>
 1351    long? IAmbientClockScheduledCallbackSource.NextScheduledCallbackStopwatchTicks
 1352    {
 1353        get
 1354        {
 1355            long nextCallbackTimeStopwatchTicks = _nextCallbackTimeStopwatchTicks;
 1356            return (nextCallbackTimeStopwatchTicks == Timeout.Infinite) ? null : nextCallbackTimeStopwatchTicks;
 1357        }
 1358    }
 1359    /// <summary>
 1360    /// Cancels a registered wait operation issued by the <see cref="System.Threading.ThreadPool.RegisterWaitForSingleOb
 1361    /// method.
 1362    /// </summary>
 1363    /// <param name="waitObject">The <see cref="System.Threading.WaitHandle"/> to be signaled.</param>
 1364    /// <returns>true if the function succeeds; otherwise, false.</returns>
 1365    public bool Unregister(WaitHandle? waitObject)
 1366    {
 1367        bool ret = _registeredWaitHandle.Unregister(waitObject);
 1368        _clock?.DeregisterTimeChangedNotificationSink(this);
 1369        return ret;
 1370    }
 1371}
 1372/// <summary>
 1373/// A static class that contains ambient replacements for <see cref="ThreadPool"/>.
 1374/// </summary>
 1375/// <remarks>
 1376/// <pitch>Ambient-clock-aware stand-ins for the <see cref="ThreadPool"/> wait registration methods — use these where wa
 1377/// <pledge>Each method mirrors its <see cref="ThreadPool"/> namesake's parameters and semantics, returning an <see cref
 1378/// <plan>Pure construction forwarding to <see cref="AmbientRegisteredWaitHandle"/>; no state of its own.</plan>
 1379/// </remarks>
 1380public static class AmbientThreadPool
 1381{
 1382    /// <summary>
 1383    /// Registers a delegate to wait for a <see cref="System.Threading.WaitHandle"/>, specifying a 32-bit signed integer
 1384    /// </summary>
 1385    /// <param name="waitHandle">Registers a delegate to wait for a System.Threading.WaitHandle, specifying a 32-bit sig
 1386    /// <param name="callback">The System.Threading.WaitOrTimerCallback delegate to call when the waitObject parameter i
 1387    /// <param name="state">The object that is passed to the delegate.</param>
 1388    /// <param name="millisecondTimeoutInterval">The time-out in milliseconds. If the millisecondsTimeOutInterval parame
 1389    /// <param name="executeOnlyOnce">true to indicate that the thread will no longer wait on the waitObject parameter a
 1390    /// <returns>The System.Threading.RegisteredWaitHandle that encapsulates the native handle.</returns>
 1391#if NET5_0_OR_GREATER
 1392    [UnsupportedOSPlatform("browser")]
 1393#endif
 1394    public static AmbientRegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitHandle, WaitOrTimerCallback cal
 1395    {
 1396        return new AmbientRegisteredWaitHandle(true, waitHandle, callback, state, millisecondTimeoutInterval, executeOnl
 1397    }
 1398    /// <summary>
 1399    /// Registers a delegate to wait for a <see cref="System.Threading.WaitHandle"/>, specifying a 32-bit unsigned integ
 1400    /// </summary>
 1401    /// <param name="waitHandle">Registers a delegate to wait for a System.Threading.WaitHandle, specifying a 32-bit sig
 1402    /// <param name="callback">The System.Threading.WaitOrTimerCallback delegate to call when the waitObject parameter i
 1403    /// <param name="state">The object that is passed to the delegate.</param>
 1404    /// <param name="millisecondTimeoutInterval">The time-out in milliseconds. If the millisecondsTimeOutInterval parame
 1405    /// <param name="executeOnlyOnce">true to indicate that the thread will no longer wait on the waitObject parameter a
 1406    /// <returns>The System.Threading.RegisteredWaitHandle that encapsulates the native handle.</returns>
 1407#if NET5_0_OR_GREATER
 1408    [UnsupportedOSPlatform("browser")]
 1409#endif
 1410    [CLSCompliant(false)]
 1411    public static AmbientRegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitHandle, WaitOrTimerCallback cal
 1412    {
 1413        return new AmbientRegisteredWaitHandle(true, waitHandle, callback, state, millisecondTimeoutInterval, executeOnl
 1414    }
 1415    /// <summary>
 1416    /// Registers a delegate to wait for a <see cref="System.Threading.WaitHandle"/>, specifying a 64-bit signed integer
 1417    /// </summary>
 1418    /// <param name="waitHandle">Registers a delegate to wait for a System.Threading.WaitHandle, specifying a 32-bit sig
 1419    /// <param name="callback">The System.Threading.WaitOrTimerCallback delegate to call when the waitObject parameter i
 1420    /// <param name="state">The object that is passed to the delegate.</param>
 1421    /// <param name="millisecondTimeoutInterval">The time-out in milliseconds. If the millisecondsTimeOutInterval parame
 1422    /// <param name="executeOnlyOnce">true to indicate that the thread will no longer wait on the waitObject parameter a
 1423    /// <returns>The System.Threading.RegisteredWaitHandle that encapsulates the native handle.</returns>
 1424#if NET5_0_OR_GREATER
 1425    [UnsupportedOSPlatform("browser")]
 1426#endif
 1427    public static AmbientRegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitHandle, WaitOrTimerCallback cal
 1428    {
 1429        return new AmbientRegisteredWaitHandle(true, waitHandle, callback, state, millisecondTimeoutInterval, executeOnl
 1430    }
 1431    /// <summary>
 1432    /// Registers a delegate to wait for a <see cref="System.Threading.WaitHandle"/>, specifying a <see cref="TimeSpan"/
 1433    /// </summary>
 1434    /// <param name="waitHandle">Registers a delegate to wait for a System.Threading.WaitHandle, specifying a 32-bit sig
 1435    /// <param name="callback">The System.Threading.WaitOrTimerCallback delegate to call when the waitObject parameter i
 1436    /// <param name="state">The object that is passed to the delegate.</param>
 1437    /// <param name="timeoutInterval">The time-out represented by a <see cref="System.TimeSpan"/>. If timeout is 0 (zero
 1438    /// <param name="executeOnlyOnce">true to indicate that the thread will no longer wait on the waitObject parameter a
 1439    /// <returns>The System.Threading.RegisteredWaitHandle that encapsulates the native handle.</returns>
 1440#if NET5_0_OR_GREATER
 1441    [UnsupportedOSPlatform("browser")]
 1442#endif
 1443    public static AmbientRegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitHandle, WaitOrTimerCallback cal
 1444    {
 1445        return new AmbientRegisteredWaitHandle(true, waitHandle, callback, state, timeoutInterval, executeOnlyOnce);
 1446    }
 1447    /// <summary>
 1448    /// Registers a delegate to wait for a <see cref="System.Threading.WaitHandle"/>, specifying a 32-bit signed integer
 1449    /// </summary>
 1450    /// <param name="waitHandle">The <see cref="System.Threading.WaitHandle"/> to register. Use a <see cref="System.Thre
 1451    /// <param name="callback">The delegate to call when the waitObject parameter is signaled.</param>
 1452    /// <param name="state">The object that is passed to the delegate.</param>
 1453    /// <param name="millisecondTimeoutInterval">The time-out represented by a System.TimeSpan. If timeout is 0 (zero), 
 1454    /// <param name="executeOnlyOnce">true to indicate that the thread will no longer wait on the waitObject parameter a
 1455    /// <returns>The <see cref="System.Threading.RegisteredWaitHandle"/> object that can be used to cancel the registere
 1456#if NET5_0_OR_GREATER
 1457    [UnsupportedOSPlatform("browser")]
 1458#endif
 1459    public static AmbientRegisteredWaitHandle UnsafeRegisterWaitForSingleObject(WaitHandle waitHandle, WaitOrTimerCallba
 1460    {
 1461        return new AmbientRegisteredWaitHandle(false, waitHandle, callback, state, millisecondTimeoutInterval, executeOn
 1462    }
 1463    /// <summary>
 1464    /// Registers a delegate to wait for a <see cref="System.Threading.WaitHandle"/>, specifying a 32-bit unsigned integ
 1465    /// </summary>
 1466    /// <param name="waitHandle">The <see cref="System.Threading.WaitHandle"/> to register. Use a <see cref="System.Thre
 1467    /// <param name="callback">The delegate to call when the waitObject parameter is signaled.</param>
 1468    /// <param name="state">The object that is passed to the delegate.</param>
 1469    /// <param name="millisecondTimeoutInterval">The time-out represented by a System.TimeSpan. If timeout is 0 (zero), 
 1470    /// <param name="executeOnlyOnce">true to indicate that the thread will no longer wait on the waitObject parameter a
 1471    /// <returns>The <see cref="System.Threading.RegisteredWaitHandle"/> object that can be used to cancel the registere
 1472#if NET5_0_OR_GREATER
 1473    [UnsupportedOSPlatform("browser")]
 1474#endif
 1475    [CLSCompliant(false)]
 1476    public static AmbientRegisteredWaitHandle UnsafeRegisterWaitForSingleObject(WaitHandle waitHandle, WaitOrTimerCallba
 1477    {
 1478        return new AmbientRegisteredWaitHandle(false, waitHandle, callback, state, millisecondTimeoutInterval, executeOn
 1479    }
 1480    /// <summary>
 1481    /// Registers a delegate to wait for a <see cref="System.Threading.WaitHandle"/>, specifying a 64-bit signed integer
 1482    /// </summary>
 1483    /// <param name="waitHandle">The <see cref="System.Threading.WaitHandle"/> to register. Use a <see cref="System.Thre
 1484    /// <param name="callback">The delegate to call when the waitObject parameter is signaled.</param>
 1485    /// <param name="state">The object that is passed to the delegate.</param>
 1486    /// <param name="millisecondTimeoutInterval">The time-out represented by a System.TimeSpan. If timeout is 0 (zero), 
 1487    /// <param name="executeOnlyOnce">true to indicate that the thread will no longer wait on the waitObject parameter a
 1488    /// <returns>The <see cref="System.Threading.RegisteredWaitHandle"/> object that can be used to cancel the registere
 1489#if NET5_0_OR_GREATER
 1490    [UnsupportedOSPlatform("browser")]
 1491#endif
 1492    public static AmbientRegisteredWaitHandle UnsafeRegisterWaitForSingleObject(WaitHandle waitHandle, WaitOrTimerCallba
 1493    {
 1494        return new AmbientRegisteredWaitHandle(false, waitHandle, callback, state, millisecondTimeoutInterval, executeOn
 1495    }
 1496    /// <summary>
 1497    /// Registers a delegate to wait for a <see cref="System.Threading.WaitHandle"/>, specifying a <see cref="System.Tim
 1498    /// </summary>
 1499    /// <param name="waitHandle">The <see cref="System.Threading.WaitHandle"/> to register. Use a <see cref="System.Thre
 1500    /// <param name="callback">The delegate to call when the waitObject parameter is signaled.</param>
 1501    /// <param name="state">The object that is passed to the delegate.</param>
 1502    /// <param name="timeoutInterval">The time-out represented by a <see cref="System.TimeSpan"/>. If timeout is 0 (zero
 1503    /// <param name="executeOnlyOnce">true to indicate that the thread will no longer wait on the waitObject parameter a
 1504    /// <returns>The <see cref="System.Threading.RegisteredWaitHandle"/> object that can be used to cancel the registere
 1505#if NET5_0_OR_GREATER
 1506    [UnsupportedOSPlatform("browser")]
 1507#endif
 1508    public static AmbientRegisteredWaitHandle UnsafeRegisterWaitForSingleObject(WaitHandle waitHandle, WaitOrTimerCallba
 1509    {
 1510        return new AmbientRegisteredWaitHandle(false, waitHandle, callback, state, timeoutInterval, executeOnlyOnce);
 1511    }
 1512}