991f53f97264022f5abbc0ff99adcdee34879140
[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             if (m_executionContext == null)
604             {
605                 m_timerCallback(m_state);
606             }
607             else
608             {
609                 ExecutionContext.Run(m_executionContext, s_callCallbackInContext, this);
610             }
611         }
612
613         private static readonly ContextCallback s_callCallbackInContext = state =>
614         {
615             TimerQueueTimer t = (TimerQueueTimer)state;
616             t.m_timerCallback(t.m_state);
617         };
618     }
619
620     //
621     // TimerHolder serves as an intermediary between Timer and TimerQueueTimer, releasing the TimerQueueTimer 
622     // if the Timer is collected.
623     // This is necessary because Timer itself cannot use its finalizer for this purpose.  If it did,
624     // then users could control timer lifetimes using GC.SuppressFinalize/ReRegisterForFinalize.
625     // You might ask, wouldn't that be a good thing?  Maybe (though it would be even better to offer this
626     // via first-class APIs), but Timer has never offered this, and adding it now would be a breaking
627     // change, because any code that happened to be suppressing finalization of Timer objects would now
628     // unwittingly be changing the lifetime of those timers.
629     //
630     internal sealed class TimerHolder
631     {
632         internal TimerQueueTimer m_timer;
633
634         public TimerHolder(TimerQueueTimer timer)
635         {
636             m_timer = timer;
637         }
638
639         ~TimerHolder()
640         {
641             //
642             // If shutdown has started, another thread may be suspended while holding the timer lock.
643             // So we can't safely close the timer.  
644             //
645             // Similarly, we should not close the timer during AD-unload's live-object finalization phase.
646             // A rude abort may have prevented us from releasing the lock.
647             //
648             // Note that in either case, the Timer still won't fire, because ThreadPool threads won't be
649             // allowed to run in this AppDomain.
650             //
651             if (Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload())
652                 return;
653
654             m_timer.Close();
655         }
656
657         public void Close()
658         {
659             m_timer.Close();
660             GC.SuppressFinalize(this);
661         }
662
663         public bool Close(WaitHandle notifyObject)
664         {
665             bool result = m_timer.Close(notifyObject);
666             GC.SuppressFinalize(this);
667             return result;
668         }
669     }
670
671
672     public sealed class Timer : MarshalByRefObject, IDisposable
673     {
674         private const UInt32 MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
675
676         private TimerHolder m_timer;
677
678         public Timer(TimerCallback callback,
679                      Object state,
680                      int dueTime,
681                      int period)
682         {
683             if (dueTime < -1)
684                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
685             if (period < -1)
686                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
687
688             TimerSetup(callback, state, (UInt32)dueTime, (UInt32)period);
689         }
690
691         public Timer(TimerCallback callback,
692                      Object state,
693                      TimeSpan dueTime,
694                      TimeSpan period)
695         {
696             long dueTm = (long)dueTime.TotalMilliseconds;
697             if (dueTm < -1)
698                 throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
699             if (dueTm > MAX_SUPPORTED_TIMEOUT)
700                 throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge);
701
702             long periodTm = (long)period.TotalMilliseconds;
703             if (periodTm < -1)
704                 throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
705             if (periodTm > MAX_SUPPORTED_TIMEOUT)
706                 throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge);
707
708             TimerSetup(callback, state, (UInt32)dueTm, (UInt32)periodTm);
709         }
710
711         [CLSCompliant(false)]
712         public Timer(TimerCallback callback,
713                      Object state,
714                      UInt32 dueTime,
715                      UInt32 period)
716         {
717             TimerSetup(callback, state, dueTime, period);
718         }
719
720         public Timer(TimerCallback callback,
721                      Object state,
722                      long dueTime,
723                      long period)
724         {
725             if (dueTime < -1)
726                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
727             if (period < -1)
728                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
729             if (dueTime > MAX_SUPPORTED_TIMEOUT)
730                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
731             if (period > MAX_SUPPORTED_TIMEOUT)
732                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
733             TimerSetup(callback, state, (UInt32)dueTime, (UInt32)period);
734         }
735
736         public Timer(TimerCallback callback)
737         {
738             int dueTime = -1;    // we want timer to be registered, but not activated.  Requires caller to call
739             int period = -1;    // Change after a timer instance is created.  This is to avoid the potential
740                                 // for a timer to be fired before the returned value is assigned to the variable,
741                                 // potentially causing the callback to reference a bogus value (if passing the timer to the callback). 
742
743             TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period);
744         }
745
746         private void TimerSetup(TimerCallback callback,
747                                 Object state,
748                                 UInt32 dueTime,
749                                 UInt32 period)
750         {
751             if (callback == null)
752                 throw new ArgumentNullException(nameof(TimerCallback));
753
754             m_timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period));
755         }
756
757         public bool Change(int dueTime, int period)
758         {
759             if (dueTime < -1)
760                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
761             if (period < -1)
762                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
763
764             return m_timer.m_timer.Change((UInt32)dueTime, (UInt32)period);
765         }
766
767         public bool Change(TimeSpan dueTime, TimeSpan period)
768         {
769             return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
770         }
771
772         [CLSCompliant(false)]
773         public bool Change(UInt32 dueTime, UInt32 period)
774         {
775             return m_timer.m_timer.Change(dueTime, period);
776         }
777
778         public bool Change(long dueTime, long period)
779         {
780             if (dueTime < -1)
781                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
782             if (period < -1)
783                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
784             if (dueTime > MAX_SUPPORTED_TIMEOUT)
785                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
786             if (period > MAX_SUPPORTED_TIMEOUT)
787                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
788
789             return m_timer.m_timer.Change((UInt32)dueTime, (UInt32)period);
790         }
791
792         public bool Dispose(WaitHandle notifyObject)
793         {
794             if (notifyObject == null)
795                 throw new ArgumentNullException(nameof(notifyObject));
796
797             return m_timer.Close(notifyObject);
798         }
799
800         public void Dispose()
801         {
802             m_timer.Close();
803         }
804     }
805 }