add comment
[mono.git] / mcs / class / corlib / System.Threading / Timer.cs
1 //
2 // System.Threading.Timer.cs
3 //
4 // Authors:
5 //      Dick Porter (dick@ximian.com)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (C) 2001, 2002 Ximian, Inc.  http://www.ximian.com
9 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
10 //
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:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
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.
29 //
30
31 using System.Runtime.InteropServices;
32 using System.Collections;
33
34 namespace System.Threading
35 {
36 #if NET_2_0
37         [ComVisible (true)]
38 #endif
39         public sealed class Timer : MarshalByRefObject, IDisposable
40         {
41 #region Timer instance fields
42                 TimerCallback callback;
43                 object state;
44                 long due_time_ms;
45                 long period_ms;
46                 long next_run; // in ticks
47                 bool disposed;
48 #endregion
49
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;
54
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;
61                 static object locker;
62 #endregion
63
64                 /* we use a static initializer to avoid race issues with the thread creation */
65                 static Timer ()
66                 {
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;
73                         scheduler.Start ();
74                 }
75
76                 static long Ticks ()
77                 {
78                         return DateTime.GetTimeMonotonic ();
79                 }
80
81                 static private void SchedulerThread ()
82                 {
83                         Thread.CurrentThread.Name = "Timer-Scheduler";
84                         while (true) {
85                                 long min_next_run = long.MaxValue;
86                                 lock (locker) {
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;
95                                                                 if (expired == null)
96                                                                         expired = new ArrayList ();
97                                                                 expired.Add (t1);
98                                                         } else {
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;
105                                                                         if (expired == null)
106                                                                                 expired = new ArrayList ();
107                                                                         expired.Add (t1);
108                                                                 }
109                                                         }
110                                                 }
111                                                 if (t1.next_run != long.MaxValue) {
112                                                         min_next_run = Math.Min (min_next_run, t1.next_run);
113                                                 }
114                                         }
115                                         if (future_queue_activated) {
116                                                 StartFutureHandler ();
117                                                 min_next_run = Math.Min (min_next_run, future_checker.next_run);
118                                         }
119                                         if (expired != null) {
120                                                 int count = expired.Count;
121                                                 for (int i = 0; i < count; ++i) {
122                                                         jobs.Remove (expired [i]);
123                                                 }
124                                                 expired.Clear ();
125                                                 if (count > 50)
126                                                         expired = null;
127                                         }
128                                 }
129
130                                 const bool exit_context =
131 #if MONOTOUCH
132                                         // MonoTouch doesn't support remoting,
133                                         // so avoid calling into the remoting infrastructure.
134                                         false;
135 #else
136                                         true;
137 #endif
138
139                                 if (min_next_run != long.MaxValue) {
140                                         long diff = min_next_run - Ticks ();
141                                         if (diff >= 0)
142                                                 change_event.WaitOne ((int)(diff / TimeSpan.TicksPerMillisecond), exit_context);
143                                 } else {
144                                         change_event.WaitOne (Timeout.Infinite, exit_context);
145                                 }
146                         }
147                 }
148
149                 public Timer (TimerCallback callback, object state, int dueTime, int period)
150                 {
151                         if (dueTime < -1)
152                                 throw new ArgumentOutOfRangeException ("dueTime");
153
154                         if (period < -1)
155                                 throw new ArgumentOutOfRangeException ("period");
156
157                         Init (callback, state, dueTime, period);
158                 }
159
160                 public Timer (TimerCallback callback, object state, long dueTime, long period)
161                 {
162                         if (dueTime < -1)
163                                 throw new ArgumentOutOfRangeException ("dueTime");
164
165                         if (period < -1)
166                                 throw new ArgumentOutOfRangeException ("period");
167
168                         Init (callback, state, dueTime, period);
169                 }
170
171                 public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
172                         : this (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds)
173                 {
174                 }
175
176                 [CLSCompliant(false)]
177                 public Timer (TimerCallback callback, object state, uint dueTime, uint period)
178                         : this (callback, state, (long) dueTime, (long) period)
179                 {
180                 }
181
182 #if NET_2_0
183                 public Timer (TimerCallback callback)
184                 {
185                         Init (callback, this, Timeout.Infinite, Timeout.Infinite);
186                 }
187 #endif
188
189                 void Init (TimerCallback callback, object state, long dueTime, long period)
190                 {
191                         if (callback == null)
192                                 throw new ArgumentNullException ("callback");
193                         
194                         this.callback = callback;
195                         this.state = state;
196
197                         Change (dueTime, period);
198                 }
199
200                 public bool Change (int dueTime, int period)
201                 {
202                         return Change ((long)dueTime, (long)period);
203                 }
204
205                 // FIXME: handle this inside the scheduler, so no additional timer is ever active
206                 static void CheckFuture (object state) {
207                         lock (locker) {
208                                 ArrayList moved = null;
209                                 long now = Ticks ();
210                                 foreach (Timer t1 in future_jobs.Keys) {
211                                         if (t1.next_run <= now + FutureTimeTicks) {
212                                                 if (moved == null)
213                                                         moved = new ArrayList ();
214                                                 moved.Add (t1);
215                                                 jobs [t1] = t1;
216                                         }
217                                 }
218                                 if (moved != null) {
219                                         int count = moved.Count;
220                                         for (int i = 0; i < count; ++i) {
221                                                 future_jobs.Remove (moved [i]);
222                                         }
223                                         moved.Clear ();
224                                         change_event.Set ();
225                                 }
226                                 // no point in keeping this helper timer running
227                                 if (future_jobs.Count == 0) {
228                                         future_checker.Dispose ();
229                                         future_checker = null;
230                                 }
231                         }
232                 }
233
234                 static void StartFutureHandler ()
235                 {
236                         if (future_checker == null)
237                                 future_checker = new Timer (CheckFuture, null, FutureTime - 500, FutureTime - 500);
238                 }
239
240                 public bool Change (long dueTime, long period)
241                 {
242                         if(dueTime > 4294967294)
243                                 throw new NotSupportedException ("Due time too large");
244
245                         if(period > 4294967294)
246                                 throw new NotSupportedException ("Period too large");
247
248                         if (dueTime < -1)
249                                 throw new ArgumentOutOfRangeException ("dueTime");
250
251                         if (period < -1)
252                                 throw new ArgumentOutOfRangeException ("period");
253
254                         if (disposed)
255                                 return false;
256
257                         due_time_ms = dueTime;
258                         period_ms = period;
259                         long now = Ticks ();
260                         if (dueTime == 0) {
261                                 next_run = now;
262                         } else if (dueTime == Timeout.Infinite) {
263                                 next_run = long.MaxValue;
264                         } else {
265                                 next_run = dueTime * TimeSpan.TicksPerMillisecond + now;
266                         }
267                         lock (locker) {
268                                 if (next_run != long.MaxValue) {
269                                         bool is_future = next_run - now > FutureTimeTicks;
270                                         Timer t = jobs [this] as Timer;
271                                         if (t == null) {
272                                                 t = future_jobs [this] as Timer;
273                                         } else {
274                                                 if (is_future) {
275                                                         future_jobs [this] = this;
276                                                         jobs.Remove (this);
277                                                 }
278                                         }
279                                         if (t == null) {
280                                                 if (is_future)
281                                                         future_jobs [this] = this;
282                                                 else
283                                                         jobs [this] = this;
284                                         }
285                                         if (is_future)
286                                                 StartFutureHandler ();
287                                         change_event.Set ();
288                                 } else {
289                                         jobs.Remove (this);
290                                         future_jobs.Remove (this);
291                                 }
292                         }
293                         return true;
294                 }
295
296                 public bool Change (TimeSpan dueTime, TimeSpan period)
297                 {
298                         return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
299                 }
300
301                 [CLSCompliant(false)]
302                 public bool Change (uint dueTime, uint period)
303                 {
304                         if (dueTime > Int32.MaxValue)
305                                 throw new NotSupportedException ("Due time too large");
306
307                         if (period > Int32.MaxValue)
308                                 throw new NotSupportedException ("Period too large");
309
310                         return Change ((long) dueTime, (long) period);
311                 }
312
313                 public void Dispose ()
314                 {
315                         disposed = true;
316                         lock (locker) {
317                                 jobs.Remove (this);
318                                 future_jobs.Remove (this);
319                         }
320                 }
321
322                 public bool Dispose (WaitHandle notifyObject)
323                 {
324                         Dispose ();
325                         NativeEventCalls.SetEvent_internal (notifyObject.Handle);
326                         return true;
327                 }
328
329         }
330 }
331