Reduce Execution Context Save+Restore (#15629)
[platform/upstream/coreclr.git] / src / mscorlib / src / System / Threading / Timer.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 //
6
7 namespace System.Threading
8 {
9     using System;
10     using System.Security;
11     using Microsoft.Win32;
12     using System.Runtime.CompilerServices;
13     using System.Runtime.InteropServices;
14     using System.Runtime.ConstrainedExecution;
15     using System.Runtime.Versioning;
16     using System.Diagnostics;
17     using System.Diagnostics.Tracing;
18     using Microsoft.Win32.SafeHandles;
19
20
21
22     public delegate void TimerCallback(Object state);
23
24     //
25     // TimerQueue maintains a list of active timers in this AppDomain.  We use a single native timer, supplied by the VM,
26     // to schedule all managed timers in the AppDomain.
27     //
28     // Perf assumptions:  We assume that timers are created and destroyed frequently, but rarely actually fire.
29     // There are roughly two types of timer:
30     //
31     //  - timeouts for operations.  These are created and destroyed very frequently, but almost never fire, because
32     //    the whole point is that the timer only fires if something has gone wrong.
33     //
34     //  - scheduled background tasks.  These typically do fire, but they usually have quite long durations.
35     //    So the impact of spending a few extra cycles to fire these is negligible.
36     //
37     // Because of this, we want to choose a data structure with very fast insert and delete times, but we can live
38     // with linear traversal times when firing timers.
39     //
40     // The data structure we've chosen is an unordered doubly-linked list of active timers.  This gives O(1) insertion
41     // and removal, and O(N) traversal when finding expired timers.
42     //
43     // Note that all instance methods of this class require that the caller hold a lock on the TimerQueue instance.
44     //
45     internal class TimerQueue
46     {
47         #region Shared TimerQueue instances
48
49         public static TimerQueue[] Instances { get; } = CreateTimerQueues();
50
51         private TimerQueue(int id)
52         {
53             m_id = id;
54         }
55
56         private static TimerQueue[] CreateTimerQueues()
57         {
58             var queues = new TimerQueue[Environment.ProcessorCount];
59             for (int i = 0; i < queues.Length; i++)
60             {
61                 queues[i] = new TimerQueue(i);
62             }
63             return queues;
64         }
65
66         #endregion
67
68         #region interface to native per-AppDomain timer
69
70         //
71         // We need to keep our notion of time synchronized with the calls to SleepEx that drive
72         // the underlying native timer.  In Win8, SleepEx does not count the time the machine spends
73         // sleeping/hibernating.  Environment.TickCount (GetTickCount) *does* count that time,
74         // so we will get out of sync with SleepEx if we use that method.
75         //
76         // So, on Win8, we use QueryUnbiasedInterruptTime instead; this does not count time spent
77         // in sleep/hibernate mode.
78         //
79         private static int TickCount
80         {
81             get
82             {
83 #if !FEATURE_PAL
84                 if (Environment.IsWindows8OrAbove)
85                 {
86                     ulong time100ns;
87
88                     bool result = Win32Native.QueryUnbiasedInterruptTime(out time100ns);
89                     if (!result)
90                         throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
91
92                     // convert to 100ns to milliseconds, and truncate to 32 bits.
93                     return (int)(uint)(time100ns / 10000);
94                 }
95                 else
96 #endif
97                 {
98                     return Environment.TickCount;
99                 }
100             }
101         }
102
103         //
104         // We use a SafeHandle to ensure that the native timer is destroyed when the AppDomain is unloaded.
105         //
106         private class AppDomainTimerSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
107         {
108             public AppDomainTimerSafeHandle()
109                 : base(true)
110             {
111             }
112
113             protected override bool ReleaseHandle()
114             {
115                 return DeleteAppDomainTimer(handle);
116             }
117         }
118
119         private readonly int m_id; // TimerQueues[m_id] == this
120         private AppDomainTimerSafeHandle m_appDomainTimer;
121
122         private bool m_isAppDomainTimerScheduled;
123         private int m_currentAppDomainTimerStartTicks;
124         private uint m_currentAppDomainTimerDuration;
125
126         private bool EnsureAppDomainTimerFiresBy(uint requestedDuration)
127         {
128             //
129             // The VM's timer implementation does not work well for very long-duration timers.
130             // See kb 950807.
131             // So we'll limit our native timer duration to a "small" value.
132             // This may cause us to attempt to fire timers early, but that's ok - 
133             // we'll just see that none of our timers has actually reached its due time,
134             // and schedule the native timer again.
135             //
136             const uint maxPossibleDuration = 0x0fffffff;
137             uint actualDuration = Math.Min(requestedDuration, maxPossibleDuration);
138
139             if (m_isAppDomainTimerScheduled)
140             {
141                 uint elapsed = (uint)(TickCount - m_currentAppDomainTimerStartTicks);
142                 if (elapsed >= m_currentAppDomainTimerDuration)
143                     return true; //the timer's about to fire
144
145                 uint remainingDuration = m_currentAppDomainTimerDuration - elapsed;
146                 if (actualDuration >= remainingDuration)
147                     return true; //the timer will fire earlier than this request
148             }
149
150             // If Pause is underway then do not schedule the timers
151             // A later update during resume will re-schedule
152             if (m_pauseTicks != 0)
153             {
154                 Debug.Assert(!m_isAppDomainTimerScheduled);
155                 Debug.Assert(m_appDomainTimer == null);
156                 return true;
157             }
158
159             if (m_appDomainTimer == null || m_appDomainTimer.IsInvalid)
160             {
161                 Debug.Assert(!m_isAppDomainTimerScheduled);
162                 Debug.Assert(m_id >= 0 && m_id < Instances.Length && this == Instances[m_id]);
163
164                 m_appDomainTimer = CreateAppDomainTimer(actualDuration, m_id);
165                 if (!m_appDomainTimer.IsInvalid)
166                 {
167                     m_isAppDomainTimerScheduled = true;
168                     m_currentAppDomainTimerStartTicks = TickCount;
169                     m_currentAppDomainTimerDuration = actualDuration;
170                     return true;
171                 }
172                 else
173                 {
174                     return false;
175                 }
176             }
177             else
178             {
179                 if (ChangeAppDomainTimer(m_appDomainTimer, actualDuration))
180                 {
181                     m_isAppDomainTimerScheduled = true;
182                     m_currentAppDomainTimerStartTicks = TickCount;
183                     m_currentAppDomainTimerDuration = actualDuration;
184                     return true;
185                 }
186                 else
187                 {
188                     return false;
189                 }
190             }
191         }
192
193         //
194         // The VM calls this when a native timer fires.
195         //
196         internal static void AppDomainTimerCallback(int id)
197         {
198             Debug.Assert(id >= 0 && id < Instances.Length && Instances[id].m_id == id);
199             Instances[id].FireNextTimers();
200         }
201
202         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
203         private static extern AppDomainTimerSafeHandle CreateAppDomainTimer(uint dueTime, int id);
204
205         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
206         private static extern bool ChangeAppDomainTimer(AppDomainTimerSafeHandle handle, uint dueTime);
207
208         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
209         private static extern bool DeleteAppDomainTimer(IntPtr handle);
210
211         #endregion
212
213         #region Firing timers
214
215         //
216         // The list of timers
217         //
218         private TimerQueueTimer m_timers;
219
220
221         private volatile int m_pauseTicks = 0; // Time when Pause was called
222
223
224         //
225         // Fire any timers that have expired, and update the native timer to schedule the rest of them.
226         // We're in a thread pool work item here, and if there are multiple timers to be fired, we want
227         // to queue all but the first one.  The first may can then be invoked synchronously or queued,
228         // a task left up to our caller, which might be firing timers from multiple queues.
229         //
230         private void FireNextTimers()
231         {
232             //
233             // we fire the first timer on this thread; any other timers that might have fired are queued
234             // to the ThreadPool.
235             //
236             TimerQueueTimer timerToFireOnThisThread = null;
237
238             lock (this)
239             {
240                 // prevent ThreadAbort while updating state
241                 try { }
242                 finally
243                 {
244                     //
245                     // since we got here, that means our previous timer has fired.
246                     //
247                     m_isAppDomainTimerScheduled = false;
248                     bool haveTimerToSchedule = false;
249                     uint nextAppDomainTimerDuration = uint.MaxValue;
250
251                     int nowTicks = TickCount;
252
253                     //
254                     // Sweep through all timers.  The ones that have reached their due time
255                     // will fire.  We will calculate the next native timer due time from the
256                     // other timers.
257                     //
258                     TimerQueueTimer timer = m_timers;
259                     while (timer != null)
260                     {
261                         Debug.Assert(timer.m_dueTime != Timeout.UnsignedInfinite);
262
263                         uint elapsed = (uint)(nowTicks - timer.m_startTicks);
264                         if (elapsed >= timer.m_dueTime)
265                         {
266                             //
267                             // Remember the next timer in case we delete this one
268                             //
269                             TimerQueueTimer nextTimer = timer.m_next;
270
271                             if (timer.m_period != Timeout.UnsignedInfinite)
272                             {
273                                 timer.m_startTicks = nowTicks;
274                                 uint elapsedForNextDueTime = elapsed - timer.m_dueTime;
275                                 if (elapsedForNextDueTime < timer.m_period)
276                                 {
277                                     // Discount the extra amount of time that has elapsed since the previous firing time to
278                                     // prevent timer ticks from drifting
279                                     timer.m_dueTime = timer.m_period - elapsedForNextDueTime;
280                                 }
281                                 else
282                                 {
283                                     // Enough time has elapsed to fire the timer yet again. The timer is not able to keep up
284                                     // with the short period, have it fire 1 ms from now to avoid spinning without a delay.
285                                     timer.m_dueTime = 1;
286                                 }
287
288                                 //
289                                 // This is a repeating timer; schedule it to run again.
290                                 //
291                                 if (timer.m_dueTime < nextAppDomainTimerDuration)
292                                 {
293                                     haveTimerToSchedule = true;
294                                     nextAppDomainTimerDuration = timer.m_dueTime;
295                                 }
296                             }
297                             else
298                             {
299                                 //
300                                 // Not repeating; remove it from the queue
301                                 //
302                                 DeleteTimer(timer);
303                             }
304
305                             //
306                             // If this is the first timer, we'll fire it on this thread.  Otherwise, queue it
307                             // to the ThreadPool.
308                             //
309                             if (timerToFireOnThisThread == null)
310                                 timerToFireOnThisThread = timer;
311                             else
312                                 QueueTimerCompletion(timer);
313
314                             timer = nextTimer;
315                         }
316                         else
317                         {
318                             //
319                             // This timer hasn't fired yet.  Just update the next time the native timer fires.
320                             //
321                             uint remaining = timer.m_dueTime - elapsed;
322                             if (remaining < nextAppDomainTimerDuration)
323                             {
324                                 haveTimerToSchedule = true;
325                                 nextAppDomainTimerDuration = remaining;
326                             }
327                             timer = timer.m_next;
328                         }
329                     }
330
331                     if (haveTimerToSchedule)
332                         EnsureAppDomainTimerFiresBy(nextAppDomainTimerDuration);
333                 }
334             }
335
336             //
337             // Fire the user timer outside of the lock!
338             //
339             if (timerToFireOnThisThread != null)
340                 timerToFireOnThisThread.Fire();
341         }
342
343         private static void QueueTimerCompletion(TimerQueueTimer timer)
344         {
345             // Can use "unsafe" variant because we take care of capturing and restoring the ExecutionContext.
346             ThreadPool.UnsafeQueueCustomWorkItem(timer, forceGlobal: true);
347         }
348
349         #endregion
350
351         #region Queue implementation
352
353         public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period)
354         {
355             if (timer.m_dueTime == Timeout.UnsignedInfinite)
356             {
357                 // the timer is not in the list; add it (as the head of the list).
358                 timer.m_next = m_timers;
359                 timer.m_prev = null;
360                 if (timer.m_next != null)
361                     timer.m_next.m_prev = timer;
362                 m_timers = timer;
363             }
364             timer.m_dueTime = dueTime;
365             timer.m_period = (period == 0) ? Timeout.UnsignedInfinite : period;
366             timer.m_startTicks = TickCount;
367             return EnsureAppDomainTimerFiresBy(dueTime);
368         }
369
370         public void DeleteTimer(TimerQueueTimer timer)
371         {
372             if (timer.m_dueTime != Timeout.UnsignedInfinite)
373             {
374                 if (timer.m_next != null)
375                     timer.m_next.m_prev = timer.m_prev;
376                 if (timer.m_prev != null)
377                     timer.m_prev.m_next = timer.m_next;
378                 if (m_timers == timer)
379                     m_timers = timer.m_next;
380
381                 timer.m_dueTime = Timeout.UnsignedInfinite;
382                 timer.m_period = Timeout.UnsignedInfinite;
383                 timer.m_startTicks = 0;
384                 timer.m_prev = null;
385                 timer.m_next = null;
386             }
387         }
388
389         #endregion
390     }
391
392     //
393     // A timer in our TimerQueue.
394     //
395     internal sealed class TimerQueueTimer : IThreadPoolWorkItem
396     {
397         //
398         // The associated timer queue.
399         //
400         private readonly TimerQueue m_associatedTimerQueue;
401
402         //
403         // All fields of this class are protected by a lock on TimerQueue.Instance.
404         //
405         // The first four fields are maintained by TimerQueue itself.
406         //
407         internal TimerQueueTimer m_next;
408         internal TimerQueueTimer m_prev;
409
410         //
411         // The time, according to TimerQueue.TickCount, when this timer's current interval started.
412         //
413         internal int m_startTicks;
414
415         //
416         // Timeout.UnsignedInfinite if we are not going to fire.  Otherwise, the offset from m_startTime when we will fire.
417         //
418         internal uint m_dueTime;
419
420         //
421         // Timeout.UnsignedInfinite if we are a single-shot timer.  Otherwise, the repeat interval.
422         //
423         internal uint m_period;
424
425         //
426         // Info about the user's callback
427         //
428         private readonly TimerCallback m_timerCallback;
429         private readonly Object m_state;
430         private readonly ExecutionContext m_executionContext;
431
432
433         //
434         // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only
435         // after all pending callbacks are complete.  We set m_canceled to prevent any callbacks that
436         // are already queued from running.  We track the number of callbacks currently executing in 
437         // m_callbacksRunning.  We set m_notifyWhenNoCallbacksRunning only when m_callbacksRunning
438         // reaches zero.
439         //
440         private int m_callbacksRunning;
441         private volatile bool m_canceled;
442         private volatile WaitHandle m_notifyWhenNoCallbacksRunning;
443
444
445         internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period)
446         {
447             m_timerCallback = timerCallback;
448             m_state = state;
449             m_dueTime = Timeout.UnsignedInfinite;
450             m_period = Timeout.UnsignedInfinite;
451             m_executionContext = ExecutionContext.Capture();
452             m_associatedTimerQueue = TimerQueue.Instances[Environment.CurrentExecutionId % TimerQueue.Instances.Length];
453
454             //
455             // After the following statement, the timer may fire.  No more manipulation of timer state outside of
456             // the lock is permitted beyond this point!
457             //
458             if (dueTime != Timeout.UnsignedInfinite)
459                 Change(dueTime, period);
460         }
461
462         internal bool Change(uint dueTime, uint period)
463         {
464             bool success;
465
466             lock (m_associatedTimerQueue)
467             {
468                 if (m_canceled)
469                     throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
470
471                 // prevent ThreadAbort while updating state
472                 try { }
473                 finally
474                 {
475                     m_period = period;
476
477                     if (dueTime == Timeout.UnsignedInfinite)
478                     {
479                         m_associatedTimerQueue.DeleteTimer(this);
480                         success = true;
481                     }
482                     else
483                     {
484                         if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
485                             FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true);
486
487                         success = m_associatedTimerQueue.UpdateTimer(this, dueTime, period);
488                     }
489                 }
490             }
491
492             return success;
493         }
494
495
496         public void Close()
497         {
498             lock (m_associatedTimerQueue)
499             {
500                 // prevent ThreadAbort while updating state
501                 try { }
502                 finally
503                 {
504                     if (!m_canceled)
505                     {
506                         m_canceled = true;
507                         m_associatedTimerQueue.DeleteTimer(this);
508                     }
509                 }
510             }
511         }
512
513
514         public bool Close(WaitHandle toSignal)
515         {
516             bool success;
517             bool shouldSignal = false;
518
519             lock (m_associatedTimerQueue)
520             {
521                 // prevent ThreadAbort while updating state
522                 try { }
523                 finally
524                 {
525                     if (m_canceled)
526                     {
527                         success = false;
528                     }
529                     else
530                     {
531                         m_canceled = true;
532                         m_notifyWhenNoCallbacksRunning = toSignal;
533                         m_associatedTimerQueue.DeleteTimer(this);
534
535                         if (m_callbacksRunning == 0)
536                             shouldSignal = true;
537
538                         success = true;
539                     }
540                 }
541             }
542
543             if (shouldSignal)
544                 SignalNoCallbacksRunning();
545
546             return success;
547         }
548
549
550         internal void Fire()
551         {
552             bool canceled = false;
553
554             lock (m_associatedTimerQueue)
555             {
556                 // prevent ThreadAbort while updating state
557                 try { }
558                 finally
559                 {
560                     canceled = m_canceled;
561                     if (!canceled)
562                         m_callbacksRunning++;
563                 }
564             }
565
566             if (canceled)
567                 return;
568
569             CallCallback();
570
571             bool shouldSignal = false;
572             lock (m_associatedTimerQueue)
573             {
574                 // prevent ThreadAbort while updating state
575                 try { }
576                 finally
577                 {
578                     m_callbacksRunning--;
579                     if (m_canceled && m_callbacksRunning == 0 && m_notifyWhenNoCallbacksRunning != null)
580                         shouldSignal = true;
581                 }
582             }
583
584             if (shouldSignal)
585                 SignalNoCallbacksRunning();
586         }
587
588         void IThreadPoolWorkItem.ExecuteWorkItem() => Fire();
589
590         void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { }
591
592         internal void SignalNoCallbacksRunning()
593         {
594             Win32Native.SetEvent(m_notifyWhenNoCallbacksRunning.SafeWaitHandle);
595         }
596
597         internal void CallCallback()
598         {
599             if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
600                 FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty);
601
602             // call directly if EC flow is suppressed
603             ExecutionContext context = m_executionContext;
604             if (context == null)
605             {
606                 m_timerCallback(m_state);
607             }
608             else
609             {
610                 ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
611             }
612         }
613
614         private static readonly ContextCallback s_callCallbackInContext = state =>
615         {
616             TimerQueueTimer t = (TimerQueueTimer)state;
617             t.m_timerCallback(t.m_state);
618         };
619     }
620
621     //
622     // TimerHolder serves as an intermediary between Timer and TimerQueueTimer, releasing the TimerQueueTimer 
623     // if the Timer is collected.
624     // This is necessary because Timer itself cannot use its finalizer for this purpose.  If it did,
625     // then users could control timer lifetimes using GC.SuppressFinalize/ReRegisterForFinalize.
626     // You might ask, wouldn't that be a good thing?  Maybe (though it would be even better to offer this
627     // via first-class APIs), but Timer has never offered this, and adding it now would be a breaking
628     // change, because any code that happened to be suppressing finalization of Timer objects would now
629     // unwittingly be changing the lifetime of those timers.
630     //
631     internal sealed class TimerHolder
632     {
633         internal TimerQueueTimer m_timer;
634
635         public TimerHolder(TimerQueueTimer timer)
636         {
637             m_timer = timer;
638         }
639
640         ~TimerHolder()
641         {
642             //
643             // If shutdown has started, another thread may be suspended while holding the timer lock.
644             // So we can't safely close the timer.  
645             //
646             // Similarly, we should not close the timer during AD-unload's live-object finalization phase.
647             // A rude abort may have prevented us from releasing the lock.
648             //
649             // Note that in either case, the Timer still won't fire, because ThreadPool threads won't be
650             // allowed to run in this AppDomain.
651             //
652             if (Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload())
653                 return;
654
655             m_timer.Close();
656         }
657
658         public void Close()
659         {
660             m_timer.Close();
661             GC.SuppressFinalize(this);
662         }
663
664         public bool Close(WaitHandle notifyObject)
665         {
666             bool result = m_timer.Close(notifyObject);
667             GC.SuppressFinalize(this);
668             return result;
669         }
670     }
671
672
673     public sealed class Timer : MarshalByRefObject, IDisposable
674     {
675         private const UInt32 MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
676
677         private TimerHolder m_timer;
678
679         public Timer(TimerCallback callback,
680                      Object state,
681                      int dueTime,
682                      int period)
683         {
684             if (dueTime < -1)
685                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
686             if (period < -1)
687                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
688
689             TimerSetup(callback, state, (UInt32)dueTime, (UInt32)period);
690         }
691
692         public Timer(TimerCallback callback,
693                      Object state,
694                      TimeSpan dueTime,
695                      TimeSpan period)
696         {
697             long dueTm = (long)dueTime.TotalMilliseconds;
698             if (dueTm < -1)
699                 throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
700             if (dueTm > MAX_SUPPORTED_TIMEOUT)
701                 throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge);
702
703             long periodTm = (long)period.TotalMilliseconds;
704             if (periodTm < -1)
705                 throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
706             if (periodTm > MAX_SUPPORTED_TIMEOUT)
707                 throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge);
708
709             TimerSetup(callback, state, (UInt32)dueTm, (UInt32)periodTm);
710         }
711
712         [CLSCompliant(false)]
713         public Timer(TimerCallback callback,
714                      Object state,
715                      UInt32 dueTime,
716                      UInt32 period)
717         {
718             TimerSetup(callback, state, dueTime, period);
719         }
720
721         public Timer(TimerCallback callback,
722                      Object state,
723                      long dueTime,
724                      long period)
725         {
726             if (dueTime < -1)
727                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
728             if (period < -1)
729                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
730             if (dueTime > MAX_SUPPORTED_TIMEOUT)
731                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
732             if (period > MAX_SUPPORTED_TIMEOUT)
733                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
734             TimerSetup(callback, state, (UInt32)dueTime, (UInt32)period);
735         }
736
737         public Timer(TimerCallback callback)
738         {
739             int dueTime = -1;    // we want timer to be registered, but not activated.  Requires caller to call
740             int period = -1;    // Change after a timer instance is created.  This is to avoid the potential
741                                 // for a timer to be fired before the returned value is assigned to the variable,
742                                 // potentially causing the callback to reference a bogus value (if passing the timer to the callback). 
743
744             TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period);
745         }
746
747         private void TimerSetup(TimerCallback callback,
748                                 Object state,
749                                 UInt32 dueTime,
750                                 UInt32 period)
751         {
752             if (callback == null)
753                 throw new ArgumentNullException(nameof(TimerCallback));
754
755             m_timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period));
756         }
757
758         public bool Change(int dueTime, int period)
759         {
760             if (dueTime < -1)
761                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
762             if (period < -1)
763                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
764
765             return m_timer.m_timer.Change((UInt32)dueTime, (UInt32)period);
766         }
767
768         public bool Change(TimeSpan dueTime, TimeSpan period)
769         {
770             return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
771         }
772
773         [CLSCompliant(false)]
774         public bool Change(UInt32 dueTime, UInt32 period)
775         {
776             return m_timer.m_timer.Change(dueTime, period);
777         }
778
779         public bool Change(long dueTime, long period)
780         {
781             if (dueTime < -1)
782                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
783             if (period < -1)
784                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
785             if (dueTime > MAX_SUPPORTED_TIMEOUT)
786                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
787             if (period > MAX_SUPPORTED_TIMEOUT)
788                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
789
790             return m_timer.m_timer.Change((UInt32)dueTime, (UInt32)period);
791         }
792
793         public bool Dispose(WaitHandle notifyObject)
794         {
795             if (notifyObject == null)
796                 throw new ArgumentNullException(nameof(notifyObject));
797
798             return m_timer.Close(notifyObject);
799         }
800
801         public void Dispose()
802         {
803             m_timer.Close();
804         }
805     }
806 }