Timer.cs 37 KB

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