// // System.Threading.Timer.cs // // Authors: // Dick Porter (dick@ximian.com) // Gonzalo Paniagua Javier (gonzalo@ximian.com) // // (C) 2001, 2002 Ximian, Inc. http://www.ximian.com // Copyright (C) 2004-2009 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System.Runtime.InteropServices; using System.Collections.Generic; using System.Collections; using System.Runtime.CompilerServices; namespace System.Threading { [ComVisible (true)] public sealed class Timer : MarshalByRefObject, IDisposable { static readonly Scheduler scheduler = Scheduler.Instance; #region Timer instance fields TimerCallback callback; object state; long due_time_ms; long period_ms; long next_run; // in ticks. Only 'Scheduler' can change it except for new timers without due time. bool disposed; #endregion public Timer (TimerCallback callback, object state, int dueTime, int period) { Init (callback, state, dueTime, period); } public Timer (TimerCallback callback, object state, long dueTime, long period) { Init (callback, state, dueTime, period); } public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) { Init (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds); } [CLSCompliant(false)] public Timer (TimerCallback callback, object state, uint dueTime, uint period) { // convert all values to long - with a special case for -1 / 0xffffffff long d = (dueTime == UInt32.MaxValue) ? Timeout.Infinite : (long) dueTime; long p = (period == UInt32.MaxValue) ? Timeout.Infinite : (long) period; Init (callback, state, d, p); } public Timer (TimerCallback callback) { Init (callback, this, Timeout.Infinite, Timeout.Infinite); } void Init (TimerCallback callback, object state, long dueTime, long period) { if (callback == null) throw new ArgumentNullException ("callback"); this.callback = callback; this.state = state; Change (dueTime, period, true); } public bool Change (int dueTime, int period) { return Change (dueTime, period, false); } public bool Change (TimeSpan dueTime, TimeSpan period) { return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds, false); } [CLSCompliant(false)] public bool Change (uint dueTime, uint period) { // convert all values to long - with a special case for -1 / 0xffffffff long d = (dueTime == UInt32.MaxValue) ? Timeout.Infinite : (long) dueTime; long p = (period == UInt32.MaxValue) ? Timeout.Infinite : (long) period; return Change (d, p, false); } public void Dispose () { if (disposed) return; disposed = true; scheduler.Remove (this); } public bool Change (long dueTime, long period) { return Change (dueTime, period, false); } const long MaxValue = UInt32.MaxValue - 1; bool Change (long dueTime, long period, bool first) { if (dueTime > MaxValue) throw new ArgumentOutOfRangeException ("dueTime", "Due time too large"); if (period > MaxValue) throw new ArgumentOutOfRangeException ("period", "Period too large"); // Timeout.Infinite == -1, so this accept everything greater than -1 if (dueTime < Timeout.Infinite) throw new ArgumentOutOfRangeException ("dueTime"); if (period < Timeout.Infinite) throw new ArgumentOutOfRangeException ("period"); if (disposed) return false; due_time_ms = dueTime; period_ms = period; long nr; if (dueTime == 0) { nr = 0; // Due now } else if (dueTime < 0) { // Infinite == -1 nr = long.MaxValue; /* No need to call Change () */ if (first) { next_run = nr; return true; } } else { nr = dueTime * TimeSpan.TicksPerMillisecond + GetTimeMonotonic (); } scheduler.Change (this, nr); return true; } public bool Dispose (WaitHandle notifyObject) { if (notifyObject == null) throw new ArgumentNullException ("notifyObject"); Dispose (); NativeEventCalls.SetEvent (notifyObject.SafeWaitHandle); return true; } // extracted from ../../../../external/referencesource/mscorlib/system/threading/timer.cs internal void KeepRootedWhileScheduled() { } // TODO: Environment.TickCount should be enough as is everywhere else [MethodImplAttribute(MethodImplOptions.InternalCall)] static extern long GetTimeMonotonic (); sealed class TimerComparer : IComparer { public int Compare (object x, object y) { Timer tx = (x as Timer); if (tx == null) return -1; Timer ty = (y as Timer); if (ty == null) return 1; long result = tx.next_run - ty.next_run; if (result == 0) return x == y ? 0 : -1; return result > 0 ? 1 : -1; } } sealed class Scheduler { static Scheduler instance; SortedList list; ManualResetEvent changed; static Scheduler () { instance = new Scheduler (); } public static Scheduler Instance { get { return instance; } } private Scheduler () { changed = new ManualResetEvent (false); list = new SortedList (new TimerComparer (), 1024); Thread thread = new Thread (SchedulerThread); thread.IsBackground = true; thread.Start (); } public void Remove (Timer timer) { // We do not keep brand new items or those with no due time. if (timer.next_run == 0 || timer.next_run == Int64.MaxValue) return; lock (this) { // If this is the next item due (index = 0), the scheduler will wake up and find nothing. // No need to Pulse () InternalRemove (timer); } } public void Change (Timer timer, long new_next_run) { bool wake = false; lock (this) { InternalRemove (timer); if (new_next_run == Int64.MaxValue) { timer.next_run = new_next_run; return; } if (!timer.disposed) { // We should only change next_run after removing and before adding timer.next_run = new_next_run; Add (timer); // If this timer is next in line, wake up the scheduler wake = (list.GetByIndex (0) == timer); } } if (wake) changed.Set (); } // lock held by caller int FindByDueTime (long nr) { int min = 0; int max = list.Count - 1; if (max < 0) return -1; if (max < 20) { while (min <= max) { Timer t = (Timer) list.GetByIndex (min); if (t.next_run == nr) return min; if (t.next_run > nr) return -1; min++; } return -1; } while (min <= max) { int half = min + ((max - min) >> 1); Timer t = (Timer) list.GetByIndex (half); if (nr == t.next_run) return half; if (nr > t.next_run) min = half + 1; else max = half - 1; } return -1; } // This should be the only caller to list.Add! void Add (Timer timer) { // Make sure there are no collisions (10000 ticks == 1ms, so we should be safe here) // Do not use list.IndexOfKey here. See bug #648130 int idx = FindByDueTime (timer.next_run); if (idx != -1) { bool up = (Int64.MaxValue - timer.next_run) > 20000 ? true : false; while (true) { idx++; if (up) timer.next_run++; else timer.next_run--; if (idx >= list.Count) break; Timer t2 = (Timer) list.GetByIndex (idx); if (t2.next_run != timer.next_run) break; } } list.Add (timer, timer); //PrintList (); } int InternalRemove (Timer timer) { int idx = list.IndexOfKey (timer); if (idx >= 0) list.RemoveAt (idx); return idx; } static void TimerCB (object o) { Timer timer = (Timer) o; timer.callback (timer.state); } void SchedulerThread () { Thread.CurrentThread.Name = "Timer-Scheduler"; var new_time = new List (512); while (true) { int ms_wait = -1; long ticks = GetTimeMonotonic (); lock (this) { changed.Reset (); //PrintList (); int i; int count = list.Count; for (i = 0; i < count; i++) { Timer timer = (Timer) list.GetByIndex (i); if (timer.next_run > ticks) break; list.RemoveAt (i); count--; i--; ThreadPool.UnsafeQueueUserWorkItem (TimerCB, timer); long period = timer.period_ms; long due_time = timer.due_time_ms; bool no_more = (period == -1 || ((period == 0 || period == Timeout.Infinite) && due_time != Timeout.Infinite)); if (no_more) { timer.next_run = Int64.MaxValue; } else { timer.next_run = GetTimeMonotonic () + TimeSpan.TicksPerMillisecond * timer.period_ms; new_time.Add (timer); } } // Reschedule timers with a new due time count = new_time.Count; for (i = 0; i < count; i++) { Timer timer = new_time [i]; Add (timer); } new_time.Clear (); ShrinkIfNeeded (new_time, 512); // Shrink the list int capacity = list.Capacity; count = list.Count; if (capacity > 1024 && count > 0 && (capacity / count) > 3) list.Capacity = count * 2; long min_next_run = Int64.MaxValue; if (list.Count > 0) min_next_run = ((Timer) list.GetByIndex (0)).next_run; //PrintList (); ms_wait = -1; if (min_next_run != Int64.MaxValue) { long diff = (min_next_run - GetTimeMonotonic ()) / TimeSpan.TicksPerMillisecond; if (diff > Int32.MaxValue) ms_wait = Int32.MaxValue - 1; else { ms_wait = (int)(diff); if (ms_wait < 0) ms_wait = 0; } } } // Wait until due time or a timer is changed and moves from/to the first place in the list. changed.WaitOne (ms_wait); } } void ShrinkIfNeeded (List list, int initial) { int capacity = list.Capacity; int count = list.Count; if (capacity > initial && count > 0 && (capacity / count) > 3) list.Capacity = count * 2; } /* void PrintList () { Console.WriteLine ("BEGIN--"); for (int i = 0; i < list.Count; i++) { Timer timer = (Timer) list.GetByIndex (i); Console.WriteLine ("{0}: {1}", i, timer.next_run); } Console.WriteLine ("END----"); } */ } } }