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]);
130 const bool exit_context =
132 // MonoTouch doesn't support remoting,
133 // so avoid calling into the remoting infrastructure.
139 if (min_next_run != long.MaxValue) {
140 long diff = min_next_run - Ticks ();
142 change_event.WaitOne ((int)(diff / TimeSpan.TicksPerMillisecond), exit_context);
144 change_event.WaitOne (Timeout.Infinite, exit_context);
149 public Timer (TimerCallback callback, object state, int dueTime, int period)
152 throw new ArgumentOutOfRangeException ("dueTime");
155 throw new ArgumentOutOfRangeException ("period");
157 Init (callback, state, dueTime, period);
160 public Timer (TimerCallback callback, object state, long dueTime, long period)
163 throw new ArgumentOutOfRangeException ("dueTime");
166 throw new ArgumentOutOfRangeException ("period");
168 Init (callback, state, dueTime, period);
171 public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
172 : this (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds)
176 [CLSCompliant(false)]
177 public Timer (TimerCallback callback, object state, uint dueTime, uint period)
178 : this (callback, state, (long) dueTime, (long) period)
183 public Timer (TimerCallback callback)
185 Init (callback, this, Timeout.Infinite, Timeout.Infinite);
189 void Init (TimerCallback callback, object state, long dueTime, long period)
191 if (callback == null)
192 throw new ArgumentNullException ("callback");
194 this.callback = callback;
197 Change (dueTime, period);
200 public bool Change (int dueTime, int period)
202 return Change ((long)dueTime, (long)period);
205 // FIXME: handle this inside the scheduler, so no additional timer is ever active
206 static void CheckFuture (object state) {
208 ArrayList moved = null;
210 foreach (Timer t1 in future_jobs.Keys) {
211 if (t1.next_run <= now + FutureTimeTicks) {
213 moved = new ArrayList ();
219 int count = moved.Count;
220 for (int i = 0; i < count; ++i) {
221 future_jobs.Remove (moved [i]);
226 // no point in keeping this helper timer running
227 if (future_jobs.Count == 0) {
228 future_checker.Dispose ();
229 future_checker = null;
234 static void StartFutureHandler ()
236 if (future_checker == null)
237 future_checker = new Timer (CheckFuture, null, FutureTime - 500, FutureTime - 500);
240 public bool Change (long dueTime, long period)
242 if(dueTime > 4294967294)
243 throw new NotSupportedException ("Due time too large");
245 if(period > 4294967294)
246 throw new NotSupportedException ("Period too large");
249 throw new ArgumentOutOfRangeException ("dueTime");
252 throw new ArgumentOutOfRangeException ("period");
257 due_time_ms = dueTime;
262 } else if (dueTime == Timeout.Infinite) {
263 next_run = long.MaxValue;
265 next_run = dueTime * TimeSpan.TicksPerMillisecond + now;
268 if (next_run != long.MaxValue) {
269 bool is_future = next_run - now > FutureTimeTicks;
270 Timer t = jobs [this] as Timer;
272 t = future_jobs [this] as Timer;
275 future_jobs [this] = this;
281 future_jobs [this] = this;
286 StartFutureHandler ();
290 future_jobs.Remove (this);
296 public bool Change (TimeSpan dueTime, TimeSpan period)
298 return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
301 [CLSCompliant(false)]
302 public bool Change (uint dueTime, uint period)
304 if (dueTime > Int32.MaxValue)
305 throw new NotSupportedException ("Due time too large");
307 if (period > Int32.MaxValue)
308 throw new NotSupportedException ("Period too large");
310 return Change ((long) dueTime, (long) period);
313 public void Dispose ()
318 future_jobs.Remove (this);
322 public bool Dispose (WaitHandle notifyObject)
325 NativeEventCalls.SetEvent_internal (notifyObject.Handle);