PageRenderTime 526ms CodeModel.GetById 102ms app.highlight 215ms RepoModel.GetById 98ms app.codeStats 1ms

/mcs/class/corlib/System.Threading/Timer.cs

https://bitbucket.org/danipen/mono
C# | 414 lines | 305 code | 50 blank | 59 comment | 79 complexity | 5aed467a738c2234be9f7cb63876fb17 MD5 | raw file
  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-2009 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
 31using System.Runtime.InteropServices;
 32using System.Collections.Generic;
 33using System.Collections;
 34
 35namespace System.Threading
 36{
 37	[ComVisible (true)]
 38	public sealed class Timer
 39		: MarshalByRefObject, IDisposable
 40	{
 41		static readonly Scheduler scheduler = Scheduler.Instance;
 42#region Timer instance fields
 43		TimerCallback callback;
 44		object state;
 45		long due_time_ms;
 46		long period_ms;
 47		long next_run; // in ticks. Only 'Scheduler' can change it except for new timers without due time.
 48		bool disposed;
 49#endregion
 50		public Timer (TimerCallback callback, object state, int dueTime, int period)
 51		{
 52			Init (callback, state, dueTime, period);
 53		}
 54
 55		public Timer (TimerCallback callback, object state, long dueTime, long period)
 56		{
 57			Init (callback, state, dueTime, period);
 58		}
 59
 60		public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
 61		{
 62			Init (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
 63		}
 64
 65		[CLSCompliant(false)]
 66		public Timer (TimerCallback callback, object state, uint dueTime, uint period)
 67		{
 68			// convert all values to long - with a special case for -1 / 0xffffffff
 69			long d = (dueTime == UInt32.MaxValue) ? Timeout.Infinite : (long) dueTime;
 70			long p = (period == UInt32.MaxValue) ? Timeout.Infinite : (long) period;
 71			Init (callback, state, d, p);
 72		}
 73
 74		public Timer (TimerCallback callback)
 75		{
 76			Init (callback, this, Timeout.Infinite, Timeout.Infinite);
 77		}
 78
 79		void Init (TimerCallback callback, object state, long dueTime, long period)
 80		{
 81			if (callback == null)
 82				throw new ArgumentNullException ("callback");
 83			
 84			this.callback = callback;
 85			this.state = state;
 86
 87			Change (dueTime, period, true);
 88		}
 89
 90		public bool Change (int dueTime, int period)
 91		{
 92			return Change (dueTime, period, false);
 93		}
 94
 95		public bool Change (TimeSpan dueTime, TimeSpan period)
 96		{
 97			return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds, false);
 98		}
 99
100		[CLSCompliant(false)]
101		public bool Change (uint dueTime, uint period)
102		{
103			// convert all values to long - with a special case for -1 / 0xffffffff
104			long d = (dueTime == UInt32.MaxValue) ? Timeout.Infinite : (long) dueTime;
105			long p = (period == UInt32.MaxValue) ? Timeout.Infinite : (long) period;
106			return Change (d, p, false);
107		}
108
109		public void Dispose ()
110		{
111			if (disposed)
112				return;
113
114			disposed = true;
115			scheduler.Remove (this);
116		}
117
118		public bool Change (long dueTime, long period)
119		{
120			return Change (dueTime, period, false);
121		}
122
123		const long MaxValue = UInt32.MaxValue - 1;
124
125		bool Change (long dueTime, long period, bool first)
126		{
127			if (dueTime > MaxValue)
128				throw new ArgumentOutOfRangeException ("dueTime", "Due time too large");
129
130			if (period > MaxValue)
131				throw new ArgumentOutOfRangeException ("period", "Period too large");
132
133			// Timeout.Infinite == -1, so this accept everything greater than -1
134			if (dueTime < Timeout.Infinite)
135				throw new ArgumentOutOfRangeException ("dueTime");
136
137			if (period < Timeout.Infinite)
138				throw new ArgumentOutOfRangeException ("period");
139
140			if (disposed)
141				return false;
142
143			due_time_ms = dueTime;
144			period_ms = period;
145			long nr;
146			if (dueTime == 0) {
147				nr = 0; // Due now
148			} else if (dueTime < 0) { // Infinite == -1
149				nr = long.MaxValue;
150				/* No need to call Change () */
151				if (first) {
152					next_run = nr;
153					return true;
154				}
155			} else {
156				nr = dueTime * TimeSpan.TicksPerMillisecond + DateTime.GetTimeMonotonic ();
157			}
158
159			scheduler.Change (this, nr);
160			return true;
161		}
162
163		public bool Dispose (WaitHandle notifyObject)
164		{
165			if (notifyObject == null)
166				throw new ArgumentNullException ("notifyObject");
167			Dispose ();
168			NativeEventCalls.SetEvent_internal (notifyObject.Handle);
169			return true;
170		}
171
172		sealed class TimerComparer : IComparer {
173			public int Compare (object x, object y)
174			{
175				Timer tx = (x as Timer);
176				if (tx == null)
177					return -1;
178				Timer ty = (y as Timer);
179				if (ty == null)
180					return 1;
181				long result = tx.next_run - ty.next_run;
182				if (result == 0)
183					return x == y ? 0 : -1;
184				return result > 0 ? 1 : -1;
185			}
186		}
187
188		sealed class Scheduler {
189			static Scheduler instance;
190			SortedList list;
191			ManualResetEvent changed;
192
193			static Scheduler ()
194			{
195				instance = new Scheduler ();
196			}
197
198			public static Scheduler Instance {
199				get { return instance; }
200			}
201
202			private Scheduler ()
203			{
204				changed = new ManualResetEvent (false);
205				list = new SortedList (new TimerComparer (), 1024);
206				Thread thread = new Thread (SchedulerThread);
207				thread.IsBackground = true;
208				thread.Start ();
209			}
210
211			public void Remove (Timer timer)
212			{
213				// We do not keep brand new items or those with no due time.
214				if (timer.next_run == 0 || timer.next_run == Int64.MaxValue)
215					return;
216
217				lock (this) {
218					// If this is the next item due (index = 0), the scheduler will wake up and find nothing.
219					// No need to Pulse ()
220					InternalRemove (timer);
221				}
222			}
223
224			public void Change (Timer timer, long new_next_run)
225			{
226				bool wake = false;
227				lock (this) {
228					InternalRemove (timer);
229					if (new_next_run == Int64.MaxValue) {
230						timer.next_run = new_next_run;
231						return;
232					}
233
234					if (!timer.disposed) {
235						// We should only change next_run after removing and before adding
236						timer.next_run = new_next_run;
237						Add (timer);
238						// If this timer is next in line, wake up the scheduler
239						wake = (list.GetByIndex (0) == timer);
240					}
241				}
242				if (wake)
243					changed.Set ();
244			}
245
246			// lock held by caller
247			int FindByDueTime (long nr)
248			{
249				int min = 0;
250				int max = list.Count - 1;
251				if (max < 0)
252					return -1;
253
254				if (max < 20) {
255					while (min <= max) {
256						Timer t = (Timer) list.GetByIndex (min);
257						if (t.next_run == nr)
258							return min;
259						if (t.next_run > nr)
260							return -1;
261						min++;
262					}
263					return -1;
264				}
265
266				while (min <= max) {
267					int half = min + ((max - min) >> 1);
268					Timer t = (Timer) list.GetByIndex (half);
269					if (nr == t.next_run)
270						return half;
271					if (nr > t.next_run)
272						min = half + 1;
273					else
274						max = half - 1;
275				}
276
277				return -1;
278			}
279
280			// This should be the only caller to list.Add!
281			void Add (Timer timer)
282			{
283				// Make sure there are no collisions (10000 ticks == 1ms, so we should be safe here)
284				// Do not use list.IndexOfKey here. See bug #648130
285				int idx = FindByDueTime (timer.next_run);
286				if (idx != -1) {
287					bool up = (Int64.MaxValue - timer.next_run) > 20000 ? true : false;
288					while (true) {
289						idx++;
290						if (up)
291							timer.next_run++;
292						else
293							timer.next_run--;
294
295						if (idx >= list.Count)
296							break;
297						Timer t2 = (Timer) list.GetByIndex (idx);
298						if (t2.next_run != timer.next_run)
299							break;
300					}
301				}
302				list.Add (timer, timer);
303				//PrintList ();
304			}
305
306			int InternalRemove (Timer timer)
307			{
308				int idx = list.IndexOfKey (timer);
309				if (idx >= 0)
310					list.RemoveAt (idx);
311				return idx;
312			}
313
314			static WaitCallback TimerCaller = new WaitCallback (TimerCB);
315			static void TimerCB (object o)
316			{
317				Timer timer = (Timer) o;
318				try {
319					timer.callback (timer.state);
320				} catch {}
321			}
322
323			void SchedulerThread ()
324			{
325				Thread.CurrentThread.Name = "Timer-Scheduler";
326				var new_time = new List<Timer> (512);
327				while (true) {
328					int ms_wait = -1;
329					long ticks = DateTime.GetTimeMonotonic ();
330					lock (this) {
331						changed.Reset ();
332						//PrintList ();
333						int i;
334						int count = list.Count;
335						for (i = 0; i < count; i++) {
336							Timer timer = (Timer) list.GetByIndex (i);
337							if (timer.next_run > ticks)
338								break;
339
340							list.RemoveAt (i);
341							count--;
342							i--;
343							ThreadPool.UnsafeQueueUserWorkItem (TimerCaller, timer);
344							long period = timer.period_ms;
345							long due_time = timer.due_time_ms;
346							bool no_more = (period == -1 || ((period == 0 || period == Timeout.Infinite) && due_time != Timeout.Infinite));
347							if (no_more) {
348								timer.next_run = Int64.MaxValue;
349							} else {
350								timer.next_run = DateTime.GetTimeMonotonic () + TimeSpan.TicksPerMillisecond * timer.period_ms;
351								new_time.Add (timer);
352							}
353						}
354
355						// Reschedule timers with a new due time
356						count = new_time.Count;
357						for (i = 0; i < count; i++) {
358							Timer timer = new_time [i];
359							Add (timer);
360						}
361						new_time.Clear ();
362						ShrinkIfNeeded (new_time, 512);
363
364						// Shrink the list
365						int capacity = list.Capacity;
366						count = list.Count;
367						if (capacity > 1024 && count > 0 && (capacity / count) > 3)
368							list.Capacity = count * 2;
369
370						long min_next_run = Int64.MaxValue;
371						if (list.Count > 0)
372							min_next_run = ((Timer) list.GetByIndex (0)).next_run;
373
374						//PrintList ();
375						ms_wait = -1;
376						if (min_next_run != Int64.MaxValue) {
377							long diff = (min_next_run - DateTime.GetTimeMonotonic ())  / TimeSpan.TicksPerMillisecond;
378							if (diff > Int32.MaxValue)
379								ms_wait = Int32.MaxValue - 1;
380							else {
381								ms_wait = (int)(diff);
382								if (ms_wait < 0)
383									ms_wait = 0;
384							}
385						}
386					}
387					// Wait until due time or a timer is changed and moves from/to the first place in the list.
388					changed.WaitOne (ms_wait);
389				}
390			}
391
392			void ShrinkIfNeeded (List<Timer> list, int initial)
393			{
394				int capacity = list.Capacity;
395				int count = list.Count;
396				if (capacity > initial && count > 0 && (capacity / count) > 3)
397					list.Capacity = count * 2;
398			}
399
400			/*
401			void PrintList ()
402			{
403				Console.WriteLine ("BEGIN--");
404				for (int i = 0; i < list.Count; i++) {
405					Timer timer = (Timer) list.GetByIndex (i);
406					Console.WriteLine ("{0}: {1}", i, timer.next_run);
407				}
408				Console.WriteLine ("END----");
409			}
410			*/
411		}
412	}
413}
414