2 // System.Threading.Timer.cs
5 // Dick Porter (dick@ximian.com)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // (C) 2001, 2002 Ximian, Inc. http://www.ximian.com
9 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Runtime.InteropServices;
32 using System.Collections;
34 namespace System.Threading
39 public sealed class Timer : MarshalByRefObject, IDisposable
41 #region Timer instance fields
42 TimerCallback callback;
46 long next_run; // in ticks
50 // timers that expire after FutureTime will be put in future_jobs
51 // 5 seconds seems reasonable, this must be at least 1 second
52 const long FutureTime = 5 * 1000;
53 const long FutureTimeTicks = FutureTime * TimeSpan.TicksPerMillisecond;
55 #region Timer static fields
56 static Thread scheduler;
57 static Hashtable jobs;
58 static Hashtable future_jobs;
59 static Timer future_checker;
60 static AutoResetEvent change_event;
64 /* we use a static initializer to avoid race issues with the thread creation */
67 change_event = new AutoResetEvent (false);
68 jobs = new Hashtable ();
69 future_jobs = new Hashtable ();
70 locker = new object ();
71 scheduler = new Thread (SchedulerThread);
72 scheduler.IsBackground = true;
78 return DateTime.GetTimeMonotonic ();
81 static private void SchedulerThread ()
83 Thread.CurrentThread.Name = "Timer-Scheduler";
85 long min_next_run = long.MaxValue;
87 ArrayList expired = null;
88 long ticks = Ticks ();
89 bool future_queue_activated = false;
90 foreach (Timer t1 in jobs.Keys) {
91 if (t1.next_run <= ticks) {
92 ThreadPool.QueueUserWorkItem (new WaitCallback (t1.callback), t1.state);
93 if (t1.period_ms == -1 || ((t1.period_ms == 0 | t1.period_ms == Timeout.Infinite) && t1.due_time_ms != Timeout.Infinite)) {
94 t1.next_run = long.MaxValue;
96 expired = new ArrayList ();
99 t1.next_run = Ticks () + TimeSpan.TicksPerMillisecond * t1.period_ms;
100 // if it expires too late, postpone to future_jobs
101 if (t1.period_ms >= FutureTime) {
102 if (future_jobs.Count == 0)
103 future_queue_activated = true;
104 future_jobs [t1] = t1;
106 expired = new ArrayList ();
111 if (t1.next_run != long.MaxValue) {
112 min_next_run = Math.Min (min_next_run, t1.next_run);
115 if (future_queue_activated) {
116 StartFutureHandler ();
117 min_next_run = Math.Min (min_next_run, future_checker.next_run);
119 if (expired != null) {
120 int count = expired.Count;
121 for (int i = 0; i < count; ++i) {
122 jobs.Remove (expired [i]);
129 if (min_next_run != long.MaxValue) {
130 long diff = min_next_run - Ticks ();
132 change_event.WaitOne ((int)(diff / TimeSpan.TicksPerMillisecond), true);
134 change_event.WaitOne (Timeout.Infinite, true);
139 public Timer (TimerCallback callback, object state, int dueTime, int period)
142 throw new ArgumentOutOfRangeException ("dueTime");
145 throw new ArgumentOutOfRangeException ("period");
147 Init (callback, state, dueTime, period);
150 public Timer (TimerCallback callback, object state, long dueTime, long period)
153 throw new ArgumentOutOfRangeException ("dueTime");
156 throw new ArgumentOutOfRangeException ("period");
158 Init (callback, state, dueTime, period);
161 public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
162 : this (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds)
166 [CLSCompliant(false)]
167 public Timer (TimerCallback callback, object state, uint dueTime, uint period)
168 : this (callback, state, (long) dueTime, (long) period)
173 public Timer (TimerCallback callback)
175 Init (callback, this, Timeout.Infinite, Timeout.Infinite);
179 void Init (TimerCallback callback, object state, long dueTime, long period)
181 if (callback == null)
182 throw new ArgumentNullException ("callback");
184 this.callback = callback;
187 Change (dueTime, period);
190 public bool Change (int dueTime, int period)
192 return Change ((long)dueTime, (long)period);
195 // FIXME: handle this inside the scheduler, so no additional timer is ever active
196 static void CheckFuture (object state) {
198 ArrayList moved = null;
200 foreach (Timer t1 in future_jobs.Keys) {
201 if (t1.next_run <= now + FutureTimeTicks) {
203 moved = new ArrayList ();
209 int count = moved.Count;
210 for (int i = 0; i < count; ++i) {
211 future_jobs.Remove (moved [i]);
216 // no point in keeping this helper timer running
217 if (future_jobs.Count == 0) {
218 future_checker.Dispose ();
219 future_checker = null;
224 static void StartFutureHandler ()
226 if (future_checker == null)
227 future_checker = new Timer (CheckFuture, null, FutureTime - 500, FutureTime - 500);
230 public bool Change (long dueTime, long period)
232 if(dueTime > 4294967294)
233 throw new NotSupportedException ("Due time too large");
235 if(period > 4294967294)
236 throw new NotSupportedException ("Period too large");
239 throw new ArgumentOutOfRangeException ("dueTime");
242 throw new ArgumentOutOfRangeException ("period");
247 due_time_ms = dueTime;
252 } else if (dueTime == Timeout.Infinite) {
253 next_run = long.MaxValue;
255 next_run = dueTime * TimeSpan.TicksPerMillisecond + now;
258 if (next_run != long.MaxValue) {
259 bool is_future = next_run - now > FutureTimeTicks;
260 Timer t = jobs [this] as Timer;
262 t = future_jobs [this] as Timer;
265 future_jobs [this] = this;
271 future_jobs [this] = this;
276 StartFutureHandler ();
280 future_jobs.Remove (this);
286 public bool Change (TimeSpan dueTime, TimeSpan period)
288 return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
291 [CLSCompliant(false)]
292 public bool Change (uint dueTime, uint period)
294 if (dueTime > Int32.MaxValue)
295 throw new NotSupportedException ("Due time too large");
297 if (period > Int32.MaxValue)
298 throw new NotSupportedException ("Period too large");
300 return Change ((long) dueTime, (long) period);
303 public void Dispose ()
308 future_jobs.Remove (this);
312 public bool Dispose (WaitHandle notifyObject)
315 NativeEventCalls.SetEvent_internal (notifyObject.Handle);