Timer.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Diagnostics;
  5. using System.Diagnostics.Tracing;
  6. using System.Runtime.ExceptionServices;
  7. using System.Threading.Tasks;
  8. namespace System.Threading
  9. {
  10. public delegate void TimerCallback(object? state);
  11. // TimerQueue maintains a list of active timers. We use a single native timer to schedule all managed timers
  12. // in the process.
  13. //
  14. // Perf assumptions: We assume that timers are created and destroyed frequently, but rarely actually fire.
  15. // There are roughly two types of timer:
  16. //
  17. // - timeouts for operations. These are created and destroyed very frequently, but almost never fire, because
  18. // the whole point is that the timer only fires if something has gone wrong.
  19. //
  20. // - scheduled background tasks. These typically do fire, but they usually have quite long durations.
  21. // So the impact of spending a few extra cycles to fire these is negligible.
  22. //
  23. // Because of this, we want to choose a data structure with very fast insert and delete times, and we can live
  24. // with linear traversal times when firing timers. However, we still want to minimize the number of timers
  25. // we need to traverse while doing the linear walk: in cases where we have lots of long-lived timers as well as
  26. // lots of short-lived timers, when the short-lived timers fire, they incur the cost of walking the long-lived ones.
  27. //
  28. // The data structure we've chosen is an unordered doubly-linked list of active timers. This gives O(1) insertion
  29. // and removal, and O(N) traversal when finding expired timers. We maintain two such lists: one for all of the
  30. // timers that'll next fire within a certain threshold, and one for the rest.
  31. //
  32. // Note that all instance methods of this class require that the caller hold a lock on the TimerQueue instance.
  33. // We partition the timers across multiple TimerQueues, each with its own lock and set of short/long lists,
  34. // in order to minimize contention when lots of threads are concurrently creating and destroying timers often.
  35. internal partial class TimerQueue
  36. {
  37. #region Shared TimerQueue instances
  38. public static TimerQueue[] Instances { get; } = CreateTimerQueues();
  39. private static TimerQueue[] CreateTimerQueues()
  40. {
  41. var queues = new TimerQueue[Environment.ProcessorCount];
  42. for (int i = 0; i < queues.Length; i++)
  43. {
  44. queues[i] = new TimerQueue(i);
  45. }
  46. return queues;
  47. }
  48. #endregion
  49. #region interface to native timer
  50. private bool _isTimerScheduled;
  51. private long _currentTimerStartTicks;
  52. private uint _currentTimerDuration;
  53. private bool EnsureTimerFiresBy(uint requestedDuration)
  54. {
  55. // The VM's timer implementation does not work well for very long-duration timers.
  56. // So we limit our native timer duration to a "small" value.
  57. // This may cause us to attempt to fire timers early, but that's ok -
  58. // we'll just see that none of our timers has actually reached its due time,
  59. // and schedule the native timer again.
  60. const uint maxPossibleDuration = 0x0fffffff;
  61. uint actualDuration = Math.Min(requestedDuration, maxPossibleDuration);
  62. if (_isTimerScheduled)
  63. {
  64. long elapsed = TickCount64 - _currentTimerStartTicks;
  65. if (elapsed >= _currentTimerDuration)
  66. return true; // the timer's about to fire
  67. uint remainingDuration = _currentTimerDuration - (uint)elapsed;
  68. if (actualDuration >= remainingDuration)
  69. return true; // the timer will fire earlier than this request
  70. }
  71. if (SetTimer(actualDuration))
  72. {
  73. _isTimerScheduled = true;
  74. _currentTimerStartTicks = TickCount64;
  75. _currentTimerDuration = actualDuration;
  76. return true;
  77. }
  78. return false;
  79. }
  80. #endregion
  81. #region Firing timers
  82. // The two lists of timers that are part of this TimerQueue. They conform to a single guarantee:
  83. // no timer in _longTimers has an absolute next firing time <= _currentAbsoluteThreshold.
  84. // That way, when FireNextTimers is invoked, we always process the short list, and we then only
  85. // process the long list if the current time is greater than _currentAbsoluteThreshold (or
  86. // if the short list is now empty and we need to process the long list to know when to next
  87. // invoke FireNextTimers).
  88. private TimerQueueTimer? _shortTimers;
  89. private TimerQueueTimer? _longTimers;
  90. // The current threshold, an absolute time where any timers scheduled to go off at or
  91. // before this time must be queued to the short list.
  92. private long _currentAbsoluteThreshold = TickCount64 + ShortTimersThresholdMilliseconds;
  93. // Default threshold that separates which timers target _shortTimers vs _longTimers. The threshold
  94. // is chosen to balance the number of timers in the small list against the frequency with which
  95. // we need to scan the long list. It's thus somewhat arbitrary and could be changed based on
  96. // observed workload demand. The larger the number, the more timers we'll likely need to enumerate
  97. // every time the timer fires, but also the more likely it is that when it does we won't
  98. // need to look at the long list because the current time will be <= _currentAbsoluteThreshold.
  99. private const int ShortTimersThresholdMilliseconds = 333;
  100. // Fire any timers that have expired, and update the native timer to schedule the rest of them.
  101. // We're in a thread pool work item here, and if there are multiple timers to be fired, we want
  102. // to queue all but the first one. The first may can then be invoked synchronously or queued,
  103. // a task left up to our caller, which might be firing timers from multiple queues.
  104. private void FireNextTimers()
  105. {
  106. // We fire the first timer on this thread; any other timers that need to be fired
  107. // are queued to the ThreadPool.
  108. TimerQueueTimer? timerToFireOnThisThread = null;
  109. lock (this)
  110. {
  111. // Since we got here, that means our previous timer has fired.
  112. _isTimerScheduled = false;
  113. bool haveTimerToSchedule = false;
  114. uint nextTimerDuration = uint.MaxValue;
  115. long nowTicks = TickCount64;
  116. // Sweep through the "short" timers. If the current tick count is greater than
  117. // the current threshold, also sweep through the "long" timers. Finally, as part
  118. // of sweeping the long timers, move anything that'll fire within the next threshold
  119. // to the short list. It's functionally ok if more timers end up in the short list
  120. // than is truly necessary (but not the opposite).
  121. TimerQueueTimer? timer = _shortTimers;
  122. for (int listNum = 0; listNum < 2; listNum++) // short == 0, long == 1
  123. {
  124. while (timer != null)
  125. {
  126. Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "A timer in the list must have a valid due time.");
  127. // Save off the next timer to examine, in case our examination of this timer results
  128. // in our deleting or moving it; we'll continue after with this saved next timer.
  129. TimerQueueTimer? next = timer._next;
  130. long elapsed = nowTicks - timer._startTicks;
  131. long remaining = timer._dueTime - elapsed;
  132. if (remaining <= 0)
  133. {
  134. // Timer is ready to fire.
  135. if (timer._period != Timeout.UnsignedInfinite)
  136. {
  137. // This is a repeating timer; schedule it to run again.
  138. // Discount the extra amount of time that has elapsed since the previous firing time to
  139. // prevent timer ticks from drifting. If enough time has already elapsed for the timer to fire
  140. // again, meaning the timer can't keep up with the short period, have it fire 1 ms from now to
  141. // avoid spinning without a delay.
  142. timer._startTicks = nowTicks;
  143. long elapsedForNextDueTime = elapsed - timer._dueTime;
  144. timer._dueTime = (elapsedForNextDueTime < timer._period) ?
  145. timer._period - (uint)elapsedForNextDueTime :
  146. 1;
  147. // Update the timer if this becomes the next timer to fire.
  148. if (timer._dueTime < nextTimerDuration)
  149. {
  150. haveTimerToSchedule = true;
  151. nextTimerDuration = timer._dueTime;
  152. }
  153. // Validate that the repeating timer is still on the right list. It's likely that
  154. // it started in the long list and was moved to the short list at some point, so
  155. // we now want to move it back to the long list if that's where it belongs. Note that
  156. // if we're currently processing the short list and move it to the long list, we may
  157. // end up revisiting it again if we also enumerate the long list, but we will have already
  158. // updated the due time appropriately so that we won't fire it again (it's also possible
  159. // but rare that we could be moving a timer from the long list to the short list here,
  160. // if the initial due time was set to be long but the timer then had a short period).
  161. bool targetShortList = (nowTicks + timer._dueTime) - _currentAbsoluteThreshold <= 0;
  162. if (timer._short != targetShortList)
  163. {
  164. MoveTimerToCorrectList(timer, targetShortList);
  165. }
  166. }
  167. else
  168. {
  169. // Not repeating; remove it from the queue
  170. DeleteTimer(timer);
  171. }
  172. // If this is the first timer, we'll fire it on this thread (after processing
  173. // all others). Otherwise, queue it to the ThreadPool.
  174. if (timerToFireOnThisThread == null)
  175. {
  176. timerToFireOnThisThread = timer;
  177. }
  178. else
  179. {
  180. ThreadPool.UnsafeQueueUserWorkItemInternal(timer, preferLocal: false);
  181. }
  182. }
  183. else
  184. {
  185. // This timer isn't ready to fire. Update the next time the native timer fires if necessary,
  186. // and move this timer to the short list if its remaining time is now at or under the threshold.
  187. if (remaining < nextTimerDuration)
  188. {
  189. haveTimerToSchedule = true;
  190. nextTimerDuration = (uint)remaining;
  191. }
  192. if (!timer._short && remaining <= ShortTimersThresholdMilliseconds)
  193. {
  194. MoveTimerToCorrectList(timer, shortList: true);
  195. }
  196. }
  197. timer = next;
  198. }
  199. // Switch to process the long list if necessary.
  200. if (listNum == 0)
  201. {
  202. // Determine how much time remains between now and the current threshold. If time remains,
  203. // we can skip processing the long list. We use > rather than >= because, although we
  204. // know that if remaining == 0 no timers in the long list will need to be fired, we
  205. // don't know without looking at them when we'll need to call FireNextTimers again. We
  206. // could in that case just set the next firing to 1, but we may as well just iterate the
  207. // long list now; otherwise, most timers created in the interim would end up in the long
  208. // list and we'd likely end up paying for another invocation of FireNextTimers that could
  209. // have been delayed longer (to whatever is the current minimum in the long list).
  210. long remaining = _currentAbsoluteThreshold - nowTicks;
  211. if (remaining > 0)
  212. {
  213. if (_shortTimers == null && _longTimers != null)
  214. {
  215. // We don't have any short timers left and we haven't examined the long list,
  216. // which means we likely don't have an accurate nextTimerDuration.
  217. // But we do know that nothing in the long list will be firing before or at _currentAbsoluteThreshold,
  218. // so we can just set nextTimerDuration to the difference between then and now.
  219. nextTimerDuration = (uint)remaining + 1;
  220. haveTimerToSchedule = true;
  221. }
  222. break;
  223. }
  224. // Switch to processing the long list.
  225. timer = _longTimers;
  226. // Now that we're going to process the long list, update the current threshold.
  227. _currentAbsoluteThreshold = nowTicks + ShortTimersThresholdMilliseconds;
  228. }
  229. }
  230. // If we still have scheduled timers, update the timer to ensure it fires
  231. // in time for the next one in line.
  232. if (haveTimerToSchedule)
  233. {
  234. EnsureTimerFiresBy(nextTimerDuration);
  235. }
  236. }
  237. // Fire the user timer outside of the lock!
  238. timerToFireOnThisThread?.Fire();
  239. }
  240. #endregion
  241. #region Queue implementation
  242. public long ActiveCount { get; private set; }
  243. public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period)
  244. {
  245. long nowTicks = TickCount64;
  246. // The timer can be put onto the short list if it's next absolute firing time
  247. // is <= the current absolute threshold.
  248. long absoluteDueTime = nowTicks + dueTime;
  249. bool shouldBeShort = _currentAbsoluteThreshold - absoluteDueTime >= 0;
  250. if (timer._dueTime == Timeout.UnsignedInfinite)
  251. {
  252. // If the timer wasn't previously scheduled, now add it to the right list.
  253. timer._short = shouldBeShort;
  254. LinkTimer(timer);
  255. ++ActiveCount;
  256. }
  257. else if (timer._short != shouldBeShort)
  258. {
  259. // If the timer was previously scheduled, but this update should cause
  260. // it to move over the list threshold in either direction, do so.
  261. UnlinkTimer(timer);
  262. timer._short = shouldBeShort;
  263. LinkTimer(timer);
  264. }
  265. timer._dueTime = dueTime;
  266. timer._period = (period == 0) ? Timeout.UnsignedInfinite : period;
  267. timer._startTicks = nowTicks;
  268. return EnsureTimerFiresBy(dueTime);
  269. }
  270. public void MoveTimerToCorrectList(TimerQueueTimer timer, bool shortList)
  271. {
  272. Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "Expected timer to be on a list.");
  273. Debug.Assert(timer._short != shortList, "Unnecessary if timer is already on the right list.");
  274. // Unlink it from whatever list it's on, change its list association, then re-link it.
  275. UnlinkTimer(timer);
  276. timer._short = shortList;
  277. LinkTimer(timer);
  278. }
  279. private void LinkTimer(TimerQueueTimer timer)
  280. {
  281. // Use timer._short to decide to which list to add.
  282. ref TimerQueueTimer? listHead = ref timer._short ? ref _shortTimers : ref _longTimers;
  283. timer._next = listHead;
  284. if (timer._next != null)
  285. {
  286. timer._next._prev = timer;
  287. }
  288. timer._prev = null;
  289. listHead = timer;
  290. }
  291. private void UnlinkTimer(TimerQueueTimer timer)
  292. {
  293. TimerQueueTimer? t = timer._next;
  294. if (t != null)
  295. {
  296. t._prev = timer._prev;
  297. }
  298. if (_shortTimers == timer)
  299. {
  300. Debug.Assert(timer._short);
  301. _shortTimers = t;
  302. }
  303. else if (_longTimers == timer)
  304. {
  305. Debug.Assert(!timer._short);
  306. _longTimers = t;
  307. }
  308. t = timer._prev;
  309. if (t != null)
  310. {
  311. t._next = timer._next;
  312. }
  313. // At this point the timer is no longer in a list, but its next and prev
  314. // references may still point to other nodes. UnlinkTimer should thus be
  315. // followed by something that overwrites those references, either with null
  316. // if deleting the timer or other nodes if adding it to another list.
  317. }
  318. public void DeleteTimer(TimerQueueTimer timer)
  319. {
  320. if (timer._dueTime != Timeout.UnsignedInfinite)
  321. {
  322. --ActiveCount;
  323. Debug.Assert(ActiveCount >= 0);
  324. UnlinkTimer(timer);
  325. timer._prev = null;
  326. timer._next = null;
  327. timer._dueTime = Timeout.UnsignedInfinite;
  328. timer._period = Timeout.UnsignedInfinite;
  329. timer._startTicks = 0;
  330. timer._short = false;
  331. }
  332. }
  333. #endregion
  334. }
  335. // A timer in our TimerQueue.
  336. internal sealed partial class TimerQueueTimer : IThreadPoolWorkItem
  337. {
  338. // The associated timer queue.
  339. private readonly TimerQueue _associatedTimerQueue;
  340. // All mutable fields of this class are protected by a lock on _associatedTimerQueue.
  341. // The first six fields are maintained by TimerQueue.
  342. // Links to the next and prev timers in the list.
  343. internal TimerQueueTimer? _next;
  344. internal TimerQueueTimer? _prev;
  345. // true if on the short list; otherwise, false.
  346. internal bool _short;
  347. // The time, according to TimerQueue.TickCount, when this timer's current interval started.
  348. internal long _startTicks;
  349. // Timeout.UnsignedInfinite if we are not going to fire. Otherwise, the offset from _startTime when we will fire.
  350. internal uint _dueTime;
  351. // Timeout.UnsignedInfinite if we are a single-shot timer. Otherwise, the repeat interval.
  352. internal uint _period;
  353. // Info about the user's callback
  354. private readonly TimerCallback _timerCallback;
  355. private readonly object? _state;
  356. private readonly ExecutionContext? _executionContext;
  357. // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only
  358. // after all pending callbacks are complete. We set _canceled to prevent any callbacks that
  359. // are already queued from running. We track the number of callbacks currently executing in
  360. // _callbacksRunning. We set _notifyWhenNoCallbacksRunning only when _callbacksRunning
  361. // reaches zero. Same applies if Timer.DisposeAsync() is used, except with a Task<bool>
  362. // instead of with a provided WaitHandle.
  363. private int _callbacksRunning;
  364. private volatile bool _canceled;
  365. private volatile object? _notifyWhenNoCallbacksRunning; // may be either WaitHandle or Task<bool>
  366. internal TimerQueueTimer(TimerCallback timerCallback, object? state, uint dueTime, uint period, bool flowExecutionContext)
  367. {
  368. _timerCallback = timerCallback;
  369. _state = state;
  370. _dueTime = Timeout.UnsignedInfinite;
  371. _period = Timeout.UnsignedInfinite;
  372. if (flowExecutionContext)
  373. {
  374. _executionContext = ExecutionContext.Capture();
  375. }
  376. _associatedTimerQueue = TimerQueue.Instances[Thread.GetCurrentProcessorId() % TimerQueue.Instances.Length];
  377. // After the following statement, the timer may fire. No more manipulation of timer state outside of
  378. // the lock is permitted beyond this point!
  379. if (dueTime != Timeout.UnsignedInfinite)
  380. Change(dueTime, period);
  381. }
  382. internal bool Change(uint dueTime, uint period)
  383. {
  384. bool success;
  385. lock (_associatedTimerQueue)
  386. {
  387. if (_canceled)
  388. throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
  389. _period = period;
  390. if (dueTime == Timeout.UnsignedInfinite)
  391. {
  392. _associatedTimerQueue.DeleteTimer(this);
  393. success = true;
  394. }
  395. else
  396. {
  397. if (FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
  398. FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true, (int)dueTime, (int)period);
  399. success = _associatedTimerQueue.UpdateTimer(this, dueTime, period);
  400. }
  401. }
  402. return success;
  403. }
  404. public void Close()
  405. {
  406. lock (_associatedTimerQueue)
  407. {
  408. if (!_canceled)
  409. {
  410. _canceled = true;
  411. _associatedTimerQueue.DeleteTimer(this);
  412. }
  413. }
  414. }
  415. public bool Close(WaitHandle toSignal)
  416. {
  417. bool success;
  418. bool shouldSignal = false;
  419. lock (_associatedTimerQueue)
  420. {
  421. if (_canceled)
  422. {
  423. success = false;
  424. }
  425. else
  426. {
  427. _canceled = true;
  428. _notifyWhenNoCallbacksRunning = toSignal;
  429. _associatedTimerQueue.DeleteTimer(this);
  430. shouldSignal = _callbacksRunning == 0;
  431. success = true;
  432. }
  433. }
  434. if (shouldSignal)
  435. SignalNoCallbacksRunning();
  436. return success;
  437. }
  438. public ValueTask CloseAsync()
  439. {
  440. lock (_associatedTimerQueue)
  441. {
  442. object? notifyWhenNoCallbacksRunning = _notifyWhenNoCallbacksRunning;
  443. // Mark the timer as canceled if it's not already.
  444. if (_canceled)
  445. {
  446. if (notifyWhenNoCallbacksRunning is WaitHandle)
  447. {
  448. // A previous call to Close(WaitHandle) stored a WaitHandle. We could try to deal with
  449. // this case by using ThreadPool.RegisterWaitForSingleObject to create a Task that'll
  450. // complete when the WaitHandle is set, but since arbitrary WaitHandle's can be supplied
  451. // by the caller, it could be for an auto-reset event or similar where that caller's
  452. // WaitOne on the WaitHandle could prevent this wrapper Task from completing. We could also
  453. // change the implementation to support storing multiple objects, but that's not pay-for-play,
  454. // and the existing Close(WaitHandle) already discounts this as being invalid, instead just
  455. // returning false if you use it multiple times. Since first calling Timer.Dispose(WaitHandle)
  456. // and then calling Timer.DisposeAsync is not something anyone is likely to or should do, we
  457. // simplify by just failing in that case.
  458. var e = new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed);
  459. e.SetCurrentStackTrace();
  460. return new ValueTask(Task.FromException(e));
  461. }
  462. }
  463. else
  464. {
  465. _canceled = true;
  466. _associatedTimerQueue.DeleteTimer(this);
  467. }
  468. // We've deleted the timer, so if there are no callbacks queued or running,
  469. // we're done and return an already-completed value task.
  470. if (_callbacksRunning == 0)
  471. {
  472. return default;
  473. }
  474. Debug.Assert(
  475. notifyWhenNoCallbacksRunning == null ||
  476. notifyWhenNoCallbacksRunning is Task<bool>);
  477. // There are callbacks queued or running, so we need to store a Task<bool>
  478. // that'll be used to signal the caller when all callbacks complete. Do so as long as
  479. // there wasn't a previous CloseAsync call that did.
  480. if (notifyWhenNoCallbacksRunning == null)
  481. {
  482. var t = new Task<bool>((object?)null, TaskCreationOptions.RunContinuationsAsynchronously);
  483. _notifyWhenNoCallbacksRunning = t;
  484. return new ValueTask(t);
  485. }
  486. // A previous CloseAsync call already hooked up a task. Just return it.
  487. return new ValueTask((Task<bool>)notifyWhenNoCallbacksRunning);
  488. }
  489. }
  490. void IThreadPoolWorkItem.Execute() => Fire(isThreadPool: true);
  491. internal void Fire(bool isThreadPool = false)
  492. {
  493. bool canceled = false;
  494. lock (_associatedTimerQueue)
  495. {
  496. canceled = _canceled;
  497. if (!canceled)
  498. _callbacksRunning++;
  499. }
  500. if (canceled)
  501. return;
  502. CallCallback(isThreadPool);
  503. bool shouldSignal = false;
  504. lock (_associatedTimerQueue)
  505. {
  506. _callbacksRunning--;
  507. if (_canceled && _callbacksRunning == 0 && _notifyWhenNoCallbacksRunning != null)
  508. shouldSignal = true;
  509. }
  510. if (shouldSignal)
  511. SignalNoCallbacksRunning();
  512. }
  513. internal void SignalNoCallbacksRunning()
  514. {
  515. object? toSignal = _notifyWhenNoCallbacksRunning;
  516. Debug.Assert(toSignal is WaitHandle || toSignal is Task<bool>);
  517. if (toSignal is WaitHandle wh)
  518. {
  519. EventWaitHandle.Set(wh.SafeWaitHandle);
  520. }
  521. else
  522. {
  523. ((Task<bool>)toSignal).TrySetResult(true);
  524. }
  525. }
  526. internal void CallCallback(bool isThreadPool)
  527. {
  528. if (FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
  529. FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty);
  530. // Call directly if EC flow is suppressed
  531. ExecutionContext? context = _executionContext;
  532. if (context == null)
  533. {
  534. _timerCallback(_state);
  535. }
  536. else
  537. {
  538. if (isThreadPool)
  539. {
  540. ExecutionContext.RunFromThreadPoolDispatchLoop(Thread.CurrentThread, context, s_callCallbackInContext, this);
  541. }
  542. else
  543. {
  544. ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
  545. }
  546. }
  547. }
  548. private static readonly ContextCallback s_callCallbackInContext = state =>
  549. {
  550. Debug.Assert(state is TimerQueueTimer);
  551. var t = (TimerQueueTimer)state;
  552. t._timerCallback(t._state);
  553. };
  554. }
  555. // TimerHolder serves as an intermediary between Timer and TimerQueueTimer, releasing the TimerQueueTimer
  556. // if the Timer is collected.
  557. // This is necessary because Timer itself cannot use its finalizer for this purpose. If it did,
  558. // then users could control timer lifetimes using GC.SuppressFinalize/ReRegisterForFinalize.
  559. // You might ask, wouldn't that be a good thing? Maybe (though it would be even better to offer this
  560. // via first-class APIs), but Timer has never offered this, and adding it now would be a breaking
  561. // change, because any code that happened to be suppressing finalization of Timer objects would now
  562. // unwittingly be changing the lifetime of those timers.
  563. internal sealed class TimerHolder
  564. {
  565. internal readonly TimerQueueTimer _timer;
  566. public TimerHolder(TimerQueueTimer timer)
  567. {
  568. _timer = timer;
  569. }
  570. ~TimerHolder()
  571. {
  572. _timer.Close();
  573. }
  574. public void Close()
  575. {
  576. _timer.Close();
  577. GC.SuppressFinalize(this);
  578. }
  579. public bool Close(WaitHandle notifyObject)
  580. {
  581. bool result = _timer.Close(notifyObject);
  582. GC.SuppressFinalize(this);
  583. return result;
  584. }
  585. public ValueTask CloseAsync()
  586. {
  587. ValueTask result = _timer.CloseAsync();
  588. GC.SuppressFinalize(this);
  589. return result;
  590. }
  591. }
  592. public sealed class Timer : MarshalByRefObject, IDisposable, IAsyncDisposable
  593. {
  594. private const uint MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
  595. private TimerHolder _timer = null!; // initialized in helper called by ctors
  596. public Timer(TimerCallback callback,
  597. object? state,
  598. int dueTime,
  599. int period) :
  600. this(callback, state, dueTime, period, flowExecutionContext: true)
  601. {
  602. }
  603. internal Timer(TimerCallback callback,
  604. object? state,
  605. int dueTime,
  606. int period,
  607. bool flowExecutionContext)
  608. {
  609. if (dueTime < -1)
  610. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  611. if (period < -1)
  612. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  613. TimerSetup(callback, state, (uint)dueTime, (uint)period, flowExecutionContext);
  614. }
  615. public Timer(TimerCallback callback,
  616. object? state,
  617. TimeSpan dueTime,
  618. TimeSpan period)
  619. {
  620. long dueTm = (long)dueTime.TotalMilliseconds;
  621. if (dueTm < -1)
  622. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  623. if (dueTm > MAX_SUPPORTED_TIMEOUT)
  624. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
  625. long periodTm = (long)period.TotalMilliseconds;
  626. if (periodTm < -1)
  627. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  628. if (periodTm > MAX_SUPPORTED_TIMEOUT)
  629. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
  630. TimerSetup(callback, state, (uint)dueTm, (uint)periodTm);
  631. }
  632. [CLSCompliant(false)]
  633. public Timer(TimerCallback callback,
  634. object? state,
  635. uint dueTime,
  636. uint period)
  637. {
  638. TimerSetup(callback, state, dueTime, period);
  639. }
  640. public Timer(TimerCallback callback,
  641. object? state,
  642. long dueTime,
  643. long period)
  644. {
  645. if (dueTime < -1)
  646. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  647. if (period < -1)
  648. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  649. if (dueTime > MAX_SUPPORTED_TIMEOUT)
  650. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
  651. if (period > MAX_SUPPORTED_TIMEOUT)
  652. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
  653. TimerSetup(callback, state, (uint)dueTime, (uint)period);
  654. }
  655. public Timer(TimerCallback callback)
  656. {
  657. const uint DueTime = unchecked((uint)(-1)); // We want timer to be registered, but not activated. Requires caller to call
  658. const uint Period = unchecked((uint)(-1)); // Change after a timer instance is created. This is to avoid the potential
  659. // for a timer to be fired before the returned value is assigned to the variable,
  660. // potentially causing the callback to reference a bogus value (if passing the timer to the callback).
  661. TimerSetup(callback, this, DueTime, Period);
  662. }
  663. private void TimerSetup(TimerCallback callback,
  664. object? state,
  665. uint dueTime,
  666. uint period,
  667. bool flowExecutionContext = true)
  668. {
  669. if (callback == null)
  670. throw new ArgumentNullException(nameof(callback));
  671. _timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period, flowExecutionContext));
  672. }
  673. public bool Change(int dueTime, int period)
  674. {
  675. if (dueTime < -1)
  676. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  677. if (period < -1)
  678. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  679. return _timer._timer.Change((uint)dueTime, (uint)period);
  680. }
  681. public bool Change(TimeSpan dueTime, TimeSpan period)
  682. {
  683. return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
  684. }
  685. [CLSCompliant(false)]
  686. public bool Change(uint dueTime, uint period)
  687. {
  688. return _timer._timer.Change(dueTime, period);
  689. }
  690. public bool Change(long dueTime, long period)
  691. {
  692. if (dueTime < -1)
  693. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  694. if (period < -1)
  695. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  696. if (dueTime > MAX_SUPPORTED_TIMEOUT)
  697. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
  698. if (period > MAX_SUPPORTED_TIMEOUT)
  699. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
  700. return _timer._timer.Change((uint)dueTime, (uint)period);
  701. }
  702. /// <summary>
  703. /// Gets the number of timers that are currently active. An active timer is registered to tick at some point in the
  704. /// future, and has not yet been canceled.
  705. /// </summary>
  706. public static long ActiveCount
  707. {
  708. get
  709. {
  710. long count = 0;
  711. foreach (TimerQueue queue in TimerQueue.Instances)
  712. {
  713. lock (queue)
  714. {
  715. count += queue.ActiveCount;
  716. }
  717. }
  718. return count;
  719. }
  720. }
  721. public bool Dispose(WaitHandle notifyObject)
  722. {
  723. if (notifyObject == null)
  724. throw new ArgumentNullException(nameof(notifyObject));
  725. return _timer.Close(notifyObject);
  726. }
  727. public void Dispose()
  728. {
  729. _timer.Close();
  730. }
  731. public ValueTask DisposeAsync()
  732. {
  733. return _timer.CloseAsync();
  734. }
  735. }
  736. }