Timer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. //
  2. // System.Threading.Timer.cs
  3. //
  4. // Authors:
  5. // Dick Porter ([email protected])
  6. // Gonzalo Paniagua Javier ([email protected])
  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. using System.Runtime.CompilerServices;
  34. using System.Threading.Tasks;
  35. namespace System.Threading
  36. {
  37. #if WASM
  38. internal static class WasmRuntime {
  39. static Dictionary<int, Action> callbacks;
  40. static int next_id;
  41. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  42. static extern void SetTimeout (int timeout, int id);
  43. internal static void ScheduleTimeout (int timeout, Action action) {
  44. if (callbacks == null)
  45. callbacks = new Dictionary<int, Action> ();
  46. int id = ++next_id;
  47. callbacks [id] = action;
  48. SetTimeout (timeout, id);
  49. }
  50. //XXX Keep this in sync with mini-wasm.c:mono_set_timeout_exec
  51. static void TimeoutCallback (int id) {
  52. var cb = callbacks [id];
  53. callbacks.Remove (id);
  54. cb ();
  55. }
  56. }
  57. #endif
  58. [ComVisible (true)]
  59. public sealed class Timer
  60. : MarshalByRefObject, IDisposable, IAsyncDisposable
  61. {
  62. static Scheduler scheduler => Scheduler.Instance;
  63. #region Timer instance fields
  64. TimerCallback callback;
  65. object state;
  66. long due_time_ms;
  67. long period_ms;
  68. long next_run; // in ticks. Only 'Scheduler' can change it except for new timers without due time.
  69. bool disposed;
  70. bool is_dead, is_added;
  71. #endregion
  72. public Timer (TimerCallback callback, object state, int dueTime, int period)
  73. {
  74. Init (callback, state, dueTime, period);
  75. }
  76. public Timer (TimerCallback callback, object state, long dueTime, long period)
  77. {
  78. Init (callback, state, dueTime, period);
  79. }
  80. public Timer (TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
  81. {
  82. Init (callback, state, (long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
  83. }
  84. [CLSCompliant(false)]
  85. public Timer (TimerCallback callback, object state, uint dueTime, uint period)
  86. {
  87. // convert all values to long - with a special case for -1 / 0xffffffff
  88. long d = (dueTime == UInt32.MaxValue) ? Timeout.Infinite : (long) dueTime;
  89. long p = (period == UInt32.MaxValue) ? Timeout.Infinite : (long) period;
  90. Init (callback, state, d, p);
  91. }
  92. public Timer (TimerCallback callback)
  93. {
  94. Init (callback, this, Timeout.Infinite, Timeout.Infinite);
  95. }
  96. void Init (TimerCallback callback, object state, long dueTime, long period)
  97. {
  98. if (callback == null)
  99. throw new ArgumentNullException ("callback");
  100. this.callback = callback;
  101. this.state = state;
  102. this.is_dead = false;
  103. this.is_added = false;
  104. Change (dueTime, period, true);
  105. }
  106. public bool Change (int dueTime, int period)
  107. {
  108. return Change (dueTime, period, false);
  109. }
  110. public bool Change (TimeSpan dueTime, TimeSpan period)
  111. {
  112. return Change ((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds, false);
  113. }
  114. [CLSCompliant(false)]
  115. public bool Change (uint dueTime, uint period)
  116. {
  117. // convert all values to long - with a special case for -1 / 0xffffffff
  118. long d = (dueTime == UInt32.MaxValue) ? Timeout.Infinite : (long) dueTime;
  119. long p = (period == UInt32.MaxValue) ? Timeout.Infinite : (long) period;
  120. return Change (d, p, false);
  121. }
  122. public void Dispose ()
  123. {
  124. if (disposed)
  125. return;
  126. disposed = true;
  127. scheduler.Remove (this);
  128. }
  129. public bool Change (long dueTime, long period)
  130. {
  131. return Change (dueTime, period, false);
  132. }
  133. const long MaxValue = UInt32.MaxValue - 1;
  134. bool Change (long dueTime, long period, bool first)
  135. {
  136. if (dueTime > MaxValue)
  137. throw new ArgumentOutOfRangeException ("dueTime", "Due time too large");
  138. if (period > MaxValue)
  139. throw new ArgumentOutOfRangeException ("period", "Period too large");
  140. // Timeout.Infinite == -1, so this accept everything greater than -1
  141. if (dueTime < Timeout.Infinite)
  142. throw new ArgumentOutOfRangeException ("dueTime");
  143. if (period < Timeout.Infinite)
  144. throw new ArgumentOutOfRangeException ("period");
  145. if (disposed)
  146. throw new ObjectDisposedException (null, Environment.GetResourceString ("ObjectDisposed_Generic"));
  147. due_time_ms = dueTime;
  148. period_ms = period;
  149. long nr;
  150. if (dueTime == 0) {
  151. nr = 0; // Due now
  152. } else if (dueTime < 0) { // Infinite == -1
  153. nr = long.MaxValue;
  154. /* No need to call Change () */
  155. if (first) {
  156. next_run = nr;
  157. return true;
  158. }
  159. } else {
  160. nr = dueTime * TimeSpan.TicksPerMillisecond + GetTimeMonotonic ();
  161. }
  162. scheduler.Change (this, nr);
  163. return true;
  164. }
  165. public bool Dispose (WaitHandle notifyObject)
  166. {
  167. if (notifyObject == null)
  168. throw new ArgumentNullException ("notifyObject");
  169. Dispose ();
  170. NativeEventCalls.SetEvent (notifyObject.SafeWaitHandle);
  171. return true;
  172. }
  173. public ValueTask DisposeAsync ()
  174. {
  175. Dispose ();
  176. return new ValueTask (Task.FromResult<object> (null));
  177. }
  178. // extracted from ../../../../external/referencesource/mscorlib/system/threading/timer.cs
  179. internal void KeepRootedWhileScheduled()
  180. {
  181. }
  182. // TODO: Environment.TickCount should be enough as is everywhere else
  183. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  184. static extern long GetTimeMonotonic ();
  185. struct TimerComparer : IComparer, IComparer<Timer> {
  186. int IComparer.Compare (object x, object y)
  187. {
  188. if (x == y)
  189. return 0;
  190. Timer tx = (x as Timer);
  191. if (tx == null)
  192. return -1;
  193. Timer ty = (y as Timer);
  194. if (ty == null)
  195. return 1;
  196. return Compare(tx, ty);
  197. }
  198. public int Compare (Timer tx, Timer ty)
  199. {
  200. long result = tx.next_run - ty.next_run;
  201. return (int)Math.Sign(result);
  202. }
  203. }
  204. sealed class Scheduler {
  205. static readonly Scheduler instance = new Scheduler ();
  206. volatile bool needReSort = true;
  207. List<Timer> list;
  208. long current_next_run = Int64.MaxValue;
  209. #if WASM
  210. bool scheduled_zero;
  211. void InitScheduler () {
  212. }
  213. void WakeupScheduler () {
  214. if (!scheduled_zero) {
  215. WasmRuntime.ScheduleTimeout (0, this.RunScheduler);
  216. scheduled_zero = true;
  217. }
  218. }
  219. void RunScheduler() {
  220. scheduled_zero = false;
  221. int ms_wait = RunSchedulerLoop ();
  222. if (ms_wait >= 0) {
  223. WasmRuntime.ScheduleTimeout (ms_wait, this.RunScheduler);
  224. if (ms_wait == 0)
  225. scheduled_zero = true;
  226. }
  227. }
  228. #else
  229. ManualResetEvent changed;
  230. void InitScheduler () {
  231. changed = new ManualResetEvent (false);
  232. Thread thread = new Thread (SchedulerThread);
  233. thread.IsBackground = true;
  234. thread.Start ();
  235. }
  236. void WakeupScheduler () {
  237. changed.Set ();
  238. }
  239. void SchedulerThread ()
  240. {
  241. Thread.CurrentThread.Name = "Timer-Scheduler";
  242. while (true) {
  243. int ms_wait = -1;
  244. lock (this) {
  245. changed.Reset ();
  246. ms_wait = RunSchedulerLoop ();
  247. }
  248. // Wait until due time or a timer is changed and moves from/to the first place in the list.
  249. changed.WaitOne (ms_wait);
  250. }
  251. }
  252. #endif
  253. public static Scheduler Instance {
  254. get { return instance; }
  255. }
  256. private Scheduler ()
  257. {
  258. list = new List<Timer> (1024);
  259. InitScheduler ();
  260. }
  261. public void Remove (Timer timer)
  262. {
  263. lock (this) {
  264. // If this is the next item due (index = 0), the scheduler will wake up and find nothing.
  265. // No need to Pulse ()
  266. InternalRemove (timer);
  267. }
  268. }
  269. public void Change (Timer timer, long new_next_run)
  270. {
  271. if (timer.is_dead)
  272. timer.is_dead = false;
  273. bool wake = false;
  274. lock (this) {
  275. needReSort = true;
  276. if (!timer.is_added) {
  277. timer.next_run = new_next_run;
  278. Add(timer);
  279. wake = current_next_run > new_next_run;
  280. } else {
  281. if (new_next_run == Int64.MaxValue) {
  282. timer.next_run = new_next_run;
  283. InternalRemove (timer);
  284. return;
  285. }
  286. if (!timer.disposed) {
  287. // We should only change next_run after removing and before adding
  288. timer.next_run = new_next_run;
  289. // FIXME
  290. wake = current_next_run > new_next_run;
  291. }
  292. }
  293. }
  294. if (wake)
  295. WakeupScheduler();
  296. }
  297. // This should be the only caller to list.Add!
  298. void Add (Timer timer)
  299. {
  300. timer.is_added = true;
  301. needReSort = true;
  302. list.Add (timer);
  303. if (list.Count == 1)
  304. WakeupScheduler();
  305. //PrintList ();
  306. }
  307. void InternalRemove (Timer timer)
  308. {
  309. timer.is_dead = true;
  310. needReSort = true;
  311. }
  312. static void TimerCB (object o)
  313. {
  314. Timer timer = (Timer) o;
  315. timer.callback (timer.state);
  316. }
  317. void FireTimer (Timer timer) {
  318. long period = timer.period_ms;
  319. long due_time = timer.due_time_ms;
  320. bool no_more = (period == -1 || ((period == 0 || period == Timeout.Infinite) && due_time != Timeout.Infinite));
  321. if (no_more) {
  322. timer.next_run = Int64.MaxValue;
  323. timer.is_dead = true;
  324. } else {
  325. timer.next_run = GetTimeMonotonic () + TimeSpan.TicksPerMillisecond * timer.period_ms;
  326. timer.is_dead = false;
  327. }
  328. ThreadPool.UnsafeQueueUserWorkItem (TimerCB, timer);
  329. }
  330. int RunSchedulerLoop () {
  331. int ms_wait = -1;
  332. int i;
  333. long ticks = GetTimeMonotonic ();
  334. var comparer = new TimerComparer();
  335. if (needReSort) {
  336. list.Sort(comparer);
  337. needReSort = false;
  338. }
  339. long min_next_run = Int64.MaxValue;
  340. for (i = 0; i < list.Count; i++) {
  341. Timer timer = list[i];
  342. if (timer.is_dead)
  343. continue;
  344. if (timer.next_run <= ticks) {
  345. FireTimer(timer);
  346. }
  347. min_next_run = Math.Min(min_next_run, timer.next_run);
  348. if ((timer.next_run > ticks) && (timer.next_run < Int64.MaxValue))
  349. timer.is_dead = false;
  350. }
  351. for (i = 0; i < list.Count; i++) {
  352. Timer timer = list[i];
  353. if (!timer.is_dead)
  354. continue;
  355. timer.is_added = false;
  356. needReSort = true;
  357. list[i] = list[list.Count - 1];
  358. i--;
  359. list.RemoveAt(list.Count - 1);
  360. if (list.Count == 0)
  361. break;
  362. }
  363. if (needReSort) {
  364. list.Sort(comparer);
  365. needReSort = false;
  366. }
  367. //PrintList ();
  368. ms_wait = -1;
  369. current_next_run = min_next_run;
  370. if (min_next_run != Int64.MaxValue) {
  371. long diff = (min_next_run - GetTimeMonotonic ()) / TimeSpan.TicksPerMillisecond;
  372. if (diff > Int32.MaxValue)
  373. ms_wait = Int32.MaxValue - 1;
  374. else {
  375. ms_wait = (int)(diff);
  376. if (ms_wait < 0)
  377. ms_wait = 0;
  378. }
  379. }
  380. return ms_wait;
  381. }
  382. /*
  383. void PrintList ()
  384. {
  385. Console.WriteLine ("BEGIN--");
  386. for (int i = 0; i < list.Count; i++) {
  387. Timer timer = (Timer) list.GetByIndex (i);
  388. Console.WriteLine ("{0}: {1}", i, timer.next_run);
  389. }
  390. Console.WriteLine ("END----");
  391. }
  392. */
  393. }
  394. }
  395. }