PageRenderTime 52ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

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