CancellationTokenSource.cs 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  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.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Threading.Tasks;
  7. namespace System.Threading
  8. {
  9. /// <summary>Signals to a <see cref="CancellationToken"/> that it should be canceled.</summary>
  10. /// <remarks>
  11. /// <para>
  12. /// <see cref="CancellationTokenSource"/> is used to instantiate a <see cref="CancellationToken"/> (via
  13. /// the source's <see cref="Token">Token</see> property) that can be handed to operations that wish to be
  14. /// notified of cancellation or that can be used to register asynchronous operations for cancellation. That
  15. /// token may have cancellation requested by calling to the source's <see cref="Cancel()"/> method.
  16. /// </para>
  17. /// <para>
  18. /// All members of this class, except <see cref="Dispose()"/>, are thread-safe and may be used
  19. /// concurrently from multiple threads.
  20. /// </para>
  21. /// </remarks>
  22. public class CancellationTokenSource : IDisposable
  23. {
  24. /// <summary>A <see cref="CancellationTokenSource"/> that's already canceled.</summary>
  25. internal static readonly CancellationTokenSource s_canceledSource = new CancellationTokenSource() { _state = NotifyingCompleteState };
  26. /// <summary>A <see cref="CancellationTokenSource"/> that's never canceled. This isn't enforced programmatically, only by usage. Do not cancel!</summary>
  27. internal static readonly CancellationTokenSource s_neverCanceledSource = new CancellationTokenSource();
  28. /// <summary>Delegate used with <see cref="Timer"/> to trigger cancellation of a <see cref="CancellationTokenSource"/>.</summary>
  29. private static readonly TimerCallback s_timerCallback = obj =>
  30. ((CancellationTokenSource)obj).NotifyCancellation(throwOnFirstException: false); // skip ThrowIfDisposed() check in Cancel()
  31. /// <summary>The number of callback partitions to use in a <see cref="CancellationTokenSource"/>. Must be a power of 2.</summary>
  32. private static readonly int s_numPartitions = GetPartitionCount();
  33. /// <summary><see cref="s_numPartitions"/> - 1, used to quickly mod into <see cref="_callbackPartitions"/>.</summary>
  34. private static readonly int s_numPartitionsMask = s_numPartitions - 1;
  35. /// <summary>The current state of the CancellationTokenSource.</summary>
  36. private volatile int _state;
  37. /// <summary>The ID of the thread currently executing the main body of CTS.Cancel()</summary>
  38. /// <remarks>
  39. /// This helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback.
  40. /// This is updated as we move between the main thread calling cts.Cancel() and any syncContexts
  41. /// that are used to actually run the callbacks.
  42. /// </remarks>
  43. private volatile int _threadIDExecutingCallbacks = -1;
  44. /// <summary>Tracks the running callback to assist ctr.Dispose() to wait for the target callback to complete.</summary>
  45. private long _executingCallbackId;
  46. /// <summary>Partitions of callbacks. Split into multiple partitions to help with scalability of registering/unregistering; each is protected by its own lock.</summary>
  47. private volatile CallbackPartition[] _callbackPartitions;
  48. /// <summary>TimerQueueTimer used by CancelAfter and Timer-related ctors. Used instead of Timer to avoid extra allocations and because the rooted behavior is desired.</summary>
  49. private volatile TimerQueueTimer _timer;
  50. /// <summary><see cref="System.Threading.WaitHandle"/> lazily initialized and returned from <see cref="WaitHandle"/>.</summary>
  51. private volatile ManualResetEvent _kernelEvent;
  52. /// <summary>Whether this <see cref="CancellationTokenSource"/> has been disposed.</summary>
  53. private bool _disposed;
  54. // legal values for _state
  55. private const int NotCanceledState = 1;
  56. private const int NotifyingState = 2;
  57. private const int NotifyingCompleteState = 3;
  58. /// <summary>Gets whether cancellation has been requested for this <see cref="CancellationTokenSource" />.</summary>
  59. /// <value>Whether cancellation has been requested for this <see cref="CancellationTokenSource" />.</value>
  60. /// <remarks>
  61. /// <para>
  62. /// This property indicates whether cancellation has been requested for this token source, such as
  63. /// due to a call to its <see cref="Cancel()"/> method.
  64. /// </para>
  65. /// <para>
  66. /// If this property returns true, it only guarantees that cancellation has been requested. It does not
  67. /// guarantee that every handler registered with the corresponding token has finished executing, nor
  68. /// that cancellation requests have finished propagating to all registered handlers. Additional
  69. /// synchronization may be required, particularly in situations where related objects are being
  70. /// canceled concurrently.
  71. /// </para>
  72. /// </remarks>
  73. public bool IsCancellationRequested => _state >= NotifyingState;
  74. /// <summary>A simple helper to determine whether cancellation has finished.</summary>
  75. internal bool IsCancellationCompleted => _state == NotifyingCompleteState;
  76. /// <summary>A simple helper to determine whether disposal has occurred.</summary>
  77. internal bool IsDisposed => _disposed;
  78. /// <summary>The ID of the thread that is running callbacks.</summary>
  79. internal int ThreadIDExecutingCallbacks
  80. {
  81. get => _threadIDExecutingCallbacks;
  82. set => _threadIDExecutingCallbacks = value;
  83. }
  84. /// <summary>Gets the <see cref="CancellationToken"/> associated with this <see cref="CancellationTokenSource"/>.</summary>
  85. /// <value>The <see cref="CancellationToken"/> associated with this <see cref="CancellationTokenSource"/>.</value>
  86. /// <exception cref="ObjectDisposedException">The token source has been disposed.</exception>
  87. public CancellationToken Token
  88. {
  89. get
  90. {
  91. ThrowIfDisposed();
  92. return new CancellationToken(this);
  93. }
  94. }
  95. internal WaitHandle WaitHandle
  96. {
  97. get
  98. {
  99. ThrowIfDisposed();
  100. // Return the handle if it was already allocated.
  101. if (_kernelEvent != null)
  102. {
  103. return _kernelEvent;
  104. }
  105. // Lazily-initialize the handle.
  106. var mre = new ManualResetEvent(false);
  107. if (Interlocked.CompareExchange(ref _kernelEvent, mre, null) != null)
  108. {
  109. mre.Dispose();
  110. }
  111. // There is a race condition between checking IsCancellationRequested and setting the event.
  112. // However, at this point, the kernel object definitely exists and the cases are:
  113. // 1. if IsCancellationRequested = true, then we will call Set()
  114. // 2. if IsCancellationRequested = false, then NotifyCancellation will see that the event exists, and will call Set().
  115. if (IsCancellationRequested)
  116. {
  117. _kernelEvent.Set();
  118. }
  119. return _kernelEvent;
  120. }
  121. }
  122. /// <summary>Gets the ID of the currently executing callback.</summary>
  123. internal long ExecutingCallback => Volatile.Read(ref _executingCallbackId);
  124. /// <summary>Initializes the <see cref="CancellationTokenSource"/>.</summary>
  125. public CancellationTokenSource() => _state = NotCanceledState;
  126. /// <summary>
  127. /// Constructs a <see cref="CancellationTokenSource"/> that will be canceled after a specified time span.
  128. /// </summary>
  129. /// <param name="delay">The time span to wait before canceling this <see cref="CancellationTokenSource"/></param>
  130. /// <exception cref="ArgumentOutOfRangeException">
  131. /// The exception that is thrown when <paramref name="delay"/> is less than -1 or greater than int.MaxValue.
  132. /// </exception>
  133. /// <remarks>
  134. /// <para>
  135. /// The countdown for the delay starts during the call to the constructor. When the delay expires,
  136. /// the constructed <see cref="CancellationTokenSource"/> is canceled, if it has
  137. /// not been canceled already.
  138. /// </para>
  139. /// <para>
  140. /// Subsequent calls to CancelAfter will reset the delay for the constructed
  141. /// <see cref="CancellationTokenSource"/>, if it has not been
  142. /// canceled already.
  143. /// </para>
  144. /// </remarks>
  145. public CancellationTokenSource(TimeSpan delay)
  146. {
  147. long totalMilliseconds = (long)delay.TotalMilliseconds;
  148. if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
  149. {
  150. throw new ArgumentOutOfRangeException(nameof(delay));
  151. }
  152. InitializeWithTimer((int)totalMilliseconds);
  153. }
  154. /// <summary>
  155. /// Constructs a <see cref="CancellationTokenSource"/> that will be canceled after a specified time span.
  156. /// </summary>
  157. /// <param name="millisecondsDelay">The time span to wait before canceling this <see cref="CancellationTokenSource"/></param>
  158. /// <exception cref="ArgumentOutOfRangeException">
  159. /// The exception that is thrown when <paramref name="millisecondsDelay"/> is less than -1.
  160. /// </exception>
  161. /// <remarks>
  162. /// <para>
  163. /// The countdown for the millisecondsDelay starts during the call to the constructor. When the millisecondsDelay expires,
  164. /// the constructed <see cref="CancellationTokenSource"/> is canceled (if it has
  165. /// not been canceled already).
  166. /// </para>
  167. /// <para>
  168. /// Subsequent calls to CancelAfter will reset the millisecondsDelay for the constructed
  169. /// <see cref="CancellationTokenSource"/>, if it has not been
  170. /// canceled already.
  171. /// </para>
  172. /// </remarks>
  173. public CancellationTokenSource(int millisecondsDelay)
  174. {
  175. if (millisecondsDelay < -1)
  176. {
  177. throw new ArgumentOutOfRangeException(nameof(millisecondsDelay));
  178. }
  179. InitializeWithTimer(millisecondsDelay);
  180. }
  181. /// <summary>Common initialization logic when constructing a CTS with a delay parameter</summary>
  182. private void InitializeWithTimer(int millisecondsDelay)
  183. {
  184. _state = NotCanceledState;
  185. _timer = new TimerQueueTimer(s_timerCallback, this, (uint)millisecondsDelay, Timeout.UnsignedInfinite, flowExecutionContext: false);
  186. // The timer roots this CTS instance while it's scheduled. That is by design, so
  187. // that code like:
  188. // CancellationToken ct = new CancellationTokenSource(timeout).Token;
  189. // will successfully cancel the token after the timeout.
  190. }
  191. /// <summary>Communicates a request for cancellation.</summary>
  192. /// <remarks>
  193. /// <para>
  194. /// The associated <see cref="CancellationToken" /> will be notified of the cancellation
  195. /// and will transition to a state where <see cref="CancellationToken.IsCancellationRequested"/> returns true.
  196. /// Any callbacks or cancelable operations registered with the <see cref="CancellationToken"/> will be executed.
  197. /// </para>
  198. /// <para>
  199. /// Cancelable operations and callbacks registered with the token should not throw exceptions.
  200. /// However, this overload of Cancel will aggregate any exceptions thrown into a <see cref="AggregateException"/>,
  201. /// such that one callback throwing an exception will not prevent other registered callbacks from being executed.
  202. /// </para>
  203. /// <para>
  204. /// The <see cref="ExecutionContext"/> that was captured when each callback was registered
  205. /// will be reestablished when the callback is invoked.
  206. /// </para>
  207. /// </remarks>
  208. /// <exception cref="AggregateException">An aggregate exception containing all the exceptions thrown
  209. /// by the registered callbacks on the associated <see cref="CancellationToken"/>.</exception>
  210. /// <exception cref="ObjectDisposedException">This <see cref="CancellationTokenSource"/> has been disposed.</exception>
  211. public void Cancel() => Cancel(false);
  212. /// <summary>Communicates a request for cancellation.</summary>
  213. /// <remarks>
  214. /// <para>
  215. /// The associated <see cref="CancellationToken" /> will be notified of the cancellation and will transition to a state where
  216. /// <see cref="CancellationToken.IsCancellationRequested"/> returns true. Any callbacks or cancelable operationsregistered
  217. /// with the <see cref="CancellationToken"/> will be executed.
  218. /// </para>
  219. /// <para>
  220. /// Cancelable operations and callbacks registered with the token should not throw exceptions.
  221. /// If <paramref name="throwOnFirstException"/> is true, an exception will immediately propagate out of the
  222. /// call to Cancel, preventing the remaining callbacks and cancelable operations from being processed.
  223. /// If <paramref name="throwOnFirstException"/> is false, this overload will aggregate any
  224. /// exceptions thrown into a <see cref="AggregateException"/>,
  225. /// such that one callback throwing an exception will not prevent other registered callbacks from being executed.
  226. /// </para>
  227. /// <para>
  228. /// The <see cref="ExecutionContext"/> that was captured when each callback was registered
  229. /// will be reestablished when the callback is invoked.
  230. /// </para>
  231. /// </remarks>
  232. /// <param name="throwOnFirstException">Specifies whether exceptions should immediately propagate.</param>
  233. /// <exception cref="AggregateException">An aggregate exception containing all the exceptions thrown
  234. /// by the registered callbacks on the associated <see cref="CancellationToken"/>.</exception>
  235. /// <exception cref="ObjectDisposedException">This <see cref="CancellationTokenSource"/> has been disposed.</exception>
  236. public void Cancel(bool throwOnFirstException)
  237. {
  238. ThrowIfDisposed();
  239. NotifyCancellation(throwOnFirstException);
  240. }
  241. /// <summary>Schedules a Cancel operation on this <see cref="CancellationTokenSource"/>.</summary>
  242. /// <param name="delay">The time span to wait before canceling this <see cref="CancellationTokenSource"/>.
  243. /// </param>
  244. /// <exception cref="ObjectDisposedException">The exception thrown when this <see
  245. /// cref="CancellationTokenSource"/> has been disposed.
  246. /// </exception>
  247. /// <exception cref="ArgumentOutOfRangeException">
  248. /// The exception thrown when <paramref name="delay"/> is less than -1 or
  249. /// greater than int.MaxValue.
  250. /// </exception>
  251. /// <remarks>
  252. /// <para>
  253. /// The countdown for the delay starts during this call. When the delay expires,
  254. /// this <see cref="CancellationTokenSource"/> is canceled, if it has
  255. /// not been canceled already.
  256. /// </para>
  257. /// <para>
  258. /// Subsequent calls to CancelAfter will reset the delay for this
  259. /// <see cref="CancellationTokenSource"/>, if it has not been canceled already.
  260. /// </para>
  261. /// </remarks>
  262. public void CancelAfter(TimeSpan delay)
  263. {
  264. long totalMilliseconds = (long)delay.TotalMilliseconds;
  265. if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
  266. {
  267. throw new ArgumentOutOfRangeException(nameof(delay));
  268. }
  269. CancelAfter((int)totalMilliseconds);
  270. }
  271. /// <summary>
  272. /// Schedules a Cancel operation on this <see cref="CancellationTokenSource"/>.
  273. /// </summary>
  274. /// <param name="millisecondsDelay">The time span to wait before canceling this <see
  275. /// cref="CancellationTokenSource"/>.
  276. /// </param>
  277. /// <exception cref="ObjectDisposedException">The exception thrown when this <see
  278. /// cref="CancellationTokenSource"/> has been disposed.
  279. /// </exception>
  280. /// <exception cref="ArgumentOutOfRangeException">
  281. /// The exception thrown when <paramref name="millisecondsDelay"/> is less than -1.
  282. /// </exception>
  283. /// <remarks>
  284. /// <para>
  285. /// The countdown for the millisecondsDelay starts during this call. When the millisecondsDelay expires,
  286. /// this <see cref="CancellationTokenSource"/> is canceled, if it has
  287. /// not been canceled already.
  288. /// </para>
  289. /// <para>
  290. /// Subsequent calls to CancelAfter will reset the millisecondsDelay for this
  291. /// <see cref="CancellationTokenSource"/>, if it has not been
  292. /// canceled already.
  293. /// </para>
  294. /// </remarks>
  295. public void CancelAfter(int millisecondsDelay)
  296. {
  297. ThrowIfDisposed();
  298. if (millisecondsDelay < -1)
  299. {
  300. throw new ArgumentOutOfRangeException(nameof(millisecondsDelay));
  301. }
  302. if (IsCancellationRequested)
  303. {
  304. return;
  305. }
  306. // There is a race condition here as a Cancel could occur between the check of
  307. // IsCancellationRequested and the creation of the timer. This is benign; in the
  308. // worst case, a timer will be created that has no effect when it expires.
  309. // Also, if Dispose() is called right here (after ThrowIfDisposed(), before timer
  310. // creation), it would result in a leaked Timer object (at least until the timer
  311. // expired and Disposed itself). But this would be considered bad behavior, as
  312. // Dispose() is not thread-safe and should not be called concurrently with CancelAfter().
  313. TimerQueueTimer timer = _timer;
  314. if (timer == null)
  315. {
  316. // Lazily initialize the timer in a thread-safe fashion.
  317. // Initially set to "never go off" because we don't want to take a
  318. // chance on a timer "losing" the initialization and then
  319. // cancelling the token before it (the timer) can be disposed.
  320. timer = new TimerQueueTimer(s_timerCallback, this, Timeout.UnsignedInfinite, Timeout.UnsignedInfinite, flowExecutionContext: false);
  321. TimerQueueTimer currentTimer = Interlocked.CompareExchange(ref _timer, timer, null);
  322. if (currentTimer != null)
  323. {
  324. // We did not initialize the timer. Dispose the new timer.
  325. timer.Close();
  326. timer = currentTimer;
  327. }
  328. }
  329. // It is possible that _timer has already been disposed, so we must do
  330. // the following in a try/catch block.
  331. try
  332. {
  333. timer.Change((uint)millisecondsDelay, Timeout.UnsignedInfinite);
  334. }
  335. catch (ObjectDisposedException)
  336. {
  337. // Just eat the exception. There is no other way to tell that
  338. // the timer has been disposed, and even if there were, there
  339. // would not be a good way to deal with the observe/dispose
  340. // race condition.
  341. }
  342. }
  343. /// <summary>Releases the resources used by this <see cref="CancellationTokenSource" />.</summary>
  344. /// <remarks>This method is not thread-safe for any other concurrent calls.</remarks>
  345. public void Dispose()
  346. {
  347. Dispose(true);
  348. GC.SuppressFinalize(this);
  349. }
  350. /// <summary>
  351. /// Releases the unmanaged resources used by the <see cref="CancellationTokenSource" /> class and optionally releases the managed resources.
  352. /// </summary>
  353. /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
  354. protected virtual void Dispose(bool disposing)
  355. {
  356. if (disposing && !_disposed)
  357. {
  358. // We specifically tolerate that a callback can be unregistered
  359. // after the CTS has been disposed and/or concurrently with cts.Dispose().
  360. // This is safe without locks because Dispose doesn't interact with values
  361. // in the callback partitions, only nulling out the ref to existing partitions.
  362. //
  363. // We also tolerate that a callback can be registered after the CTS has been
  364. // disposed. This is safe because InternalRegister is tolerant
  365. // of _callbackPartitions becoming null during its execution. However,
  366. // we run the acceptable risk of _callbackPartitions getting reinitialized
  367. // to non-null if there is a race between Dispose and Register, in which case this
  368. // instance may unnecessarily hold onto a registered callback. But that's no worse
  369. // than if Dispose wasn't safe to use concurrently, as Dispose would never be called,
  370. // and thus no handlers would be dropped.
  371. //
  372. // And, we tolerate Dispose being used concurrently with Cancel. This is necessary
  373. // to properly support, e.g., LinkedCancellationTokenSource, where, due to common usage patterns,
  374. // it's possible for this pairing to occur with valid usage (e.g. a component accepts
  375. // an external CancellationToken and uses CreateLinkedTokenSource to combine it with an
  376. // internal source of cancellation, then Disposes of that linked source, which could
  377. // happen at the same time the external entity is requesting cancellation).
  378. TimerQueueTimer timer = _timer;
  379. if (timer != null)
  380. {
  381. _timer = null;
  382. timer.Close(); // TimerQueueTimer.Close is thread-safe
  383. }
  384. _callbackPartitions = null; // free for GC; Cancel correctly handles a null field
  385. // If a kernel event was created via WaitHandle, we'd like to Dispose of it. However,
  386. // we only want to do so if it's not being used by Cancel concurrently. First, we
  387. // interlocked exchange it to be null, and then we check whether cancellation is currently
  388. // in progress. NotifyCancellation will only try to set the event if it exists after it's
  389. // transitioned to and while it's in the NotifyingState.
  390. if (_kernelEvent != null)
  391. {
  392. ManualResetEvent mre = Interlocked.Exchange(ref _kernelEvent, null);
  393. if (mre != null && _state != NotifyingState)
  394. {
  395. mre.Dispose();
  396. }
  397. }
  398. _disposed = true;
  399. }
  400. }
  401. /// <summary>Throws an exception if the source has been disposed.</summary>
  402. private void ThrowIfDisposed()
  403. {
  404. if (_disposed)
  405. {
  406. ThrowObjectDisposedException();
  407. }
  408. }
  409. /// <summary>Throws an <see cref="ObjectDisposedException"/>. Separated out from ThrowIfDisposed to help with inlining.</summary>
  410. private static void ThrowObjectDisposedException() =>
  411. throw new ObjectDisposedException(null, SR.CancellationTokenSource_Disposed);
  412. /// <summary>
  413. /// Registers a callback object. If cancellation has already occurred, the
  414. /// callback will have been run by the time this method returns.
  415. /// </summary>
  416. internal CancellationTokenRegistration InternalRegister(
  417. Action<object> callback, object stateForCallback, SynchronizationContext syncContext, ExecutionContext executionContext)
  418. {
  419. Debug.Assert(this != s_neverCanceledSource, "This source should never be exposed via a CancellationToken.");
  420. // If not canceled, register the handler; if canceled already, run the callback synchronously.
  421. // This also ensures that during ExecuteCallbackHandlers() there will be no mutation of the _callbackPartitions.
  422. if (!IsCancellationRequested)
  423. {
  424. // In order to enable code to not leak too many handlers, we allow Dispose to be called concurrently
  425. // with Register. While this is not a recommended practice, consumers can and do use it this way.
  426. // We don't make any guarantees about whether the CTS will hold onto the supplied callback if the CTS
  427. // has already been disposed when the callback is registered, but we try not to while at the same time
  428. // not paying any non-negligible overhead. The simple compromise is to check whether we're disposed
  429. // (not volatile), and if we see we are, to return an empty registration. If there's a race and _disposed
  430. // is false even though it's been disposed, or if the disposal request comes in after this line, we simply
  431. // run the minor risk of having _callbackPartitions reinitialized (after it was cleared to null during Dispose).
  432. if (_disposed)
  433. {
  434. return new CancellationTokenRegistration();
  435. }
  436. // Get the partitions...
  437. CallbackPartition[] partitions = _callbackPartitions;
  438. if (partitions == null)
  439. {
  440. partitions = new CallbackPartition[s_numPartitions];
  441. partitions = Interlocked.CompareExchange(ref _callbackPartitions, partitions, null) ?? partitions;
  442. }
  443. // ...and determine which partition to use.
  444. int partitionIndex = Environment.CurrentManagedThreadId & s_numPartitionsMask;
  445. Debug.Assert(partitionIndex < partitions.Length, $"Expected {partitionIndex} to be less than {partitions.Length}");
  446. CallbackPartition partition = partitions[partitionIndex];
  447. if (partition == null)
  448. {
  449. partition = new CallbackPartition(this);
  450. partition = Interlocked.CompareExchange(ref partitions[partitionIndex], partition, null) ?? partition;
  451. }
  452. // Store the callback information into the callback arrays.
  453. long id;
  454. CallbackNode node;
  455. bool lockTaken = false;
  456. partition.Lock.Enter(ref lockTaken);
  457. try
  458. {
  459. // Assign the next available unique ID.
  460. id = partition.NextAvailableId++;
  461. // Get a node, from the free list if possible or else a new one.
  462. node = partition.FreeNodeList;
  463. if (node != null)
  464. {
  465. partition.FreeNodeList = node.Next;
  466. Debug.Assert(node.Prev == null, "Nodes in the free list should all have a null Prev");
  467. // node.Next will be overwritten below so no need to set it here.
  468. }
  469. else
  470. {
  471. node = new CallbackNode(partition);
  472. }
  473. // Configure the node.
  474. node.Id = id;
  475. node.Callback = callback;
  476. node.CallbackState = stateForCallback;
  477. node.ExecutionContext = executionContext;
  478. node.SynchronizationContext = syncContext;
  479. // Add it to the callbacks list.
  480. node.Next = partition.Callbacks;
  481. if (node.Next != null)
  482. {
  483. node.Next.Prev = node;
  484. }
  485. partition.Callbacks = node;
  486. }
  487. finally
  488. {
  489. partition.Lock.Exit(useMemoryBarrier: false); // no check on lockTaken needed without thread aborts
  490. }
  491. // If cancellation hasn't been requested, return the registration.
  492. // if cancellation has been requested, try to undo the registration and run the callback
  493. // ourselves, but if we can't unregister it (e.g. the thread running Cancel snagged
  494. // our callback for execution), return the registration so that the caller can wait
  495. // for callback completion in ctr.Dispose().
  496. var ctr = new CancellationTokenRegistration(id, node);
  497. if (!IsCancellationRequested || !partition.Unregister(id, node))
  498. {
  499. return ctr;
  500. }
  501. }
  502. // Cancellation already occurred. Run the callback on this thread and return an empty registration.
  503. callback(stateForCallback);
  504. return default;
  505. }
  506. private void NotifyCancellation(bool throwOnFirstException)
  507. {
  508. // If we're the first to signal cancellation, do the main extra work.
  509. if (!IsCancellationRequested && Interlocked.CompareExchange(ref _state, NotifyingState, NotCanceledState) == NotCanceledState)
  510. {
  511. // Dispose of the timer, if any. Dispose may be running concurrently here, but TimerQueueTimer.Close is thread-safe.
  512. TimerQueueTimer timer = _timer;
  513. if (timer != null)
  514. {
  515. _timer = null;
  516. timer.Close();
  517. }
  518. // Set the event if it's been lazily initialized and hasn't yet been disposed of. Dispose may
  519. // be running concurrently, in which case either it'll have set m_kernelEvent back to null and
  520. // we won't see it here, or it'll see that we've transitioned to NOTIFYING and will skip disposing it,
  521. // leaving cleanup to finalization.
  522. _kernelEvent?.Set(); // update the MRE value.
  523. // - late enlisters to the Canceled event will have their callbacks called immediately in the Register() methods.
  524. // - Callbacks are not called inside a lock.
  525. // - After transition, no more delegates will be added to the
  526. // - list of handlers, and hence it can be consumed and cleared at leisure by ExecuteCallbackHandlers.
  527. ExecuteCallbackHandlers(throwOnFirstException);
  528. Debug.Assert(IsCancellationCompleted, "Expected cancellation to have finished");
  529. }
  530. }
  531. /// <summary>Invoke all registered callbacks.</summary>
  532. /// <remarks>The handlers are invoked synchronously in LIFO order.</remarks>
  533. private void ExecuteCallbackHandlers(bool throwOnFirstException)
  534. {
  535. Debug.Assert(IsCancellationRequested, "ExecuteCallbackHandlers should only be called after setting IsCancellationRequested->true");
  536. // Record the threadID being used for running the callbacks.
  537. ThreadIDExecutingCallbacks = Environment.CurrentManagedThreadId;
  538. // If there are no callbacks to run, we can safely exit. Any race conditions to lazy initialize it
  539. // will see IsCancellationRequested and will then run the callback themselves.
  540. CallbackPartition[] partitions = Interlocked.Exchange(ref _callbackPartitions, null);
  541. if (partitions == null)
  542. {
  543. Interlocked.Exchange(ref _state, NotifyingCompleteState);
  544. return;
  545. }
  546. List<Exception> exceptionList = null;
  547. try
  548. {
  549. // For each partition, and each callback in that partition, execute the associated handler.
  550. // We call the delegates in LIFO order on each partition so that callbacks fire 'deepest first'.
  551. // This is intended to help with nesting scenarios so that child enlisters cancel before their parents.
  552. foreach (CallbackPartition partition in partitions)
  553. {
  554. if (partition == null)
  555. {
  556. // Uninitialized partition. Nothing to do.
  557. continue;
  558. }
  559. // Iterate through all nodes in the partition. We remove each node prior
  560. // to processing it. This allows for unregistration of subsequent registrations
  561. // to still be effective even as other registrations are being invoked.
  562. while (true)
  563. {
  564. CallbackNode node;
  565. bool lockTaken = false;
  566. partition.Lock.Enter(ref lockTaken);
  567. try
  568. {
  569. // Pop the next registration from the callbacks list.
  570. node = partition.Callbacks;
  571. if (node == null)
  572. {
  573. // No more registrations to process.
  574. break;
  575. }
  576. else
  577. {
  578. Debug.Assert(node.Prev == null);
  579. if (node.Next != null) node.Next.Prev = null;
  580. partition.Callbacks = node.Next;
  581. }
  582. // Publish the intended callback ID, to ensure ctr.Dispose can tell if a wait is necessary.
  583. // This write happens while the lock is held so that Dispose is either able to successfully
  584. // unregister or is guaranteed to see an accurate executing callback ID, since it takes
  585. // the same lock to remove the node from the callback list.
  586. _executingCallbackId = node.Id;
  587. // Now that we've grabbed the Id, reset the node's Id to 0. This signals
  588. // to code unregistering that the node is no longer associated with a callback.
  589. node.Id = 0;
  590. }
  591. finally
  592. {
  593. partition.Lock.Exit(useMemoryBarrier: false); // no check on lockTaken needed without thread aborts
  594. }
  595. // Invoke the callback on this thread if there's no sync context or on the
  596. // target sync context if there is one.
  597. try
  598. {
  599. if (node.SynchronizationContext != null)
  600. {
  601. // Transition to the target syncContext and continue there.
  602. node.SynchronizationContext.Send(s =>
  603. {
  604. var n = (CallbackNode)s;
  605. n.Partition.Source.ThreadIDExecutingCallbacks = Environment.CurrentManagedThreadId;
  606. n.ExecuteCallback();
  607. }, node);
  608. ThreadIDExecutingCallbacks = Environment.CurrentManagedThreadId; // above may have altered ThreadIDExecutingCallbacks, so reset it
  609. }
  610. else
  611. {
  612. node.ExecuteCallback();
  613. }
  614. }
  615. catch (Exception ex) when (!throwOnFirstException)
  616. {
  617. // Store the exception and continue
  618. (exceptionList ?? (exceptionList = new List<Exception>())).Add(ex);
  619. }
  620. // Drop the node. While we could add it to the free list, doing so has cost (we'd need to take the lock again)
  621. // and very limited value. Since a source can only be canceled once, and after it's canceled registrations don't
  622. // need nodes, the only benefit to putting this on the free list would be if Register raced with cancellation
  623. // occurring, such that it could have used this free node but would instead need to allocate a new node (if
  624. // there wasn't another free node available).
  625. }
  626. }
  627. }
  628. finally
  629. {
  630. _state = NotifyingCompleteState;
  631. Volatile.Write(ref _executingCallbackId, 0);
  632. Interlocked.MemoryBarrier(); // for safety, prevent reorderings crossing this point and seeing inconsistent state.
  633. }
  634. if (exceptionList != null)
  635. {
  636. Debug.Assert(exceptionList.Count > 0, $"Expected {exceptionList.Count} > 0");
  637. throw new AggregateException(exceptionList);
  638. }
  639. }
  640. /// <summary>Gets the number of callback partitions to use based on the number of cores.</summary>
  641. /// <returns>A power of 2 representing the number of partitions to use.</returns>
  642. private static int GetPartitionCount()
  643. {
  644. int procs = PlatformHelper.ProcessorCount;
  645. int count =
  646. procs > 8 ? 16 : // capped at 16 to limit memory usage on larger machines
  647. procs > 4 ? 8 :
  648. procs > 2 ? 4 :
  649. procs > 1 ? 2 :
  650. 1;
  651. Debug.Assert(count > 0 && (count & (count - 1)) == 0, $"Got {count}, but expected a power of 2");
  652. return count;
  653. }
  654. /// <summary>
  655. /// Creates a <see cref="CancellationTokenSource"/> that will be in the canceled state
  656. /// when any of the source tokens are in the canceled state.
  657. /// </summary>
  658. /// <param name="token1">The first <see cref="CancellationToken">CancellationToken</see> to observe.</param>
  659. /// <param name="token2">The second <see cref="CancellationToken">CancellationToken</see> to observe.</param>
  660. /// <returns>A <see cref="CancellationTokenSource"/> that is linked
  661. /// to the source tokens.</returns>
  662. public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2) =>
  663. !token1.CanBeCanceled ? CreateLinkedTokenSource(token2) :
  664. token2.CanBeCanceled ? new Linked2CancellationTokenSource(token1, token2) :
  665. (CancellationTokenSource)new Linked1CancellationTokenSource(token1);
  666. /// <summary>
  667. /// Creates a <see cref="CancellationTokenSource"/> that will be in the canceled state
  668. /// when any of the source tokens are in the canceled state.
  669. /// </summary>
  670. /// <param name="token">The first <see cref="CancellationToken">CancellationToken</see> to observe.</param>
  671. /// <returns>A <see cref="CancellationTokenSource"/> that is linked to the source tokens.</returns>
  672. internal static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token) =>
  673. token.CanBeCanceled ? new Linked1CancellationTokenSource(token) : new CancellationTokenSource();
  674. /// <summary>
  675. /// Creates a <see cref="CancellationTokenSource"/> that will be in the canceled state
  676. /// when any of the source tokens are in the canceled state.
  677. /// </summary>
  678. /// <param name="tokens">The <see cref="CancellationToken">CancellationToken</see> instances to observe.</param>
  679. /// <returns>A <see cref="CancellationTokenSource"/> that is linked to the source tokens.</returns>
  680. /// <exception cref="System.ArgumentNullException"><paramref name="tokens"/> is null.</exception>
  681. public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens)
  682. {
  683. if (tokens == null)
  684. {
  685. throw new ArgumentNullException(nameof(tokens));
  686. }
  687. switch (tokens.Length)
  688. {
  689. case 0:
  690. throw new ArgumentException(SR.CancellationToken_CreateLinkedToken_TokensIsEmpty);
  691. case 1:
  692. return CreateLinkedTokenSource(tokens[0]);
  693. case 2:
  694. return CreateLinkedTokenSource(tokens[0], tokens[1]);
  695. default:
  696. // a defensive copy is not required as the array has value-items that have only a single reference field,
  697. // hence each item cannot be null itself, and reads of the payloads cannot be torn.
  698. return new LinkedNCancellationTokenSource(tokens);
  699. }
  700. }
  701. /// <summary>
  702. /// Wait for a single callback to complete (or, more specifically, to not be running).
  703. /// It is ok to call this method if the callback has already finished.
  704. /// Calling this method before the target callback has been selected for execution would be an error.
  705. /// </summary>
  706. internal void WaitForCallbackToComplete(long id)
  707. {
  708. var sw = new SpinWait();
  709. while (ExecutingCallback == id)
  710. {
  711. sw.SpinOnce(); // spin, as we assume callback execution is fast and that this situation is rare.
  712. }
  713. }
  714. /// <summary>
  715. /// Asynchronously wait for a single callback to complete (or, more specifically, to not be running).
  716. /// It is ok to call this method if the callback has already finished.
  717. /// Calling this method before the target callback has been selected for execution would be an error.
  718. /// </summary>
  719. internal ValueTask WaitForCallbackToCompleteAsync(long id)
  720. {
  721. // If the currently executing callback is not the target one, then the target one has already
  722. // completed and we can simply return. This should be the most common case, as the caller
  723. // calls if we're currently canceling but doesn't know what callback is running, if any.
  724. if (ExecutingCallback != id)
  725. {
  726. return default;
  727. }
  728. // The specified callback is actually running: queue a task that'll poll for the currently
  729. // executing callback to complete. In general scheduling such a work item that polls is a really
  730. // unfortunate thing to do. However, we expect this to be a rare case (disposing while the associated
  731. // callback is running), and brief when it happens (so the polling will be minimal), and making
  732. // this work with a callback mechanism will add additional cost to other more common cases.
  733. return new ValueTask(Task.Factory.StartNew(s =>
  734. {
  735. var state = (Tuple<CancellationTokenSource, long>)s;
  736. state.Item1.WaitForCallbackToComplete(state.Item2);
  737. }, Tuple.Create(this, id), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default));
  738. }
  739. private sealed class Linked1CancellationTokenSource : CancellationTokenSource
  740. {
  741. private readonly CancellationTokenRegistration _reg1;
  742. internal Linked1CancellationTokenSource(CancellationToken token1)
  743. {
  744. _reg1 = token1.UnsafeRegister(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
  745. }
  746. protected override void Dispose(bool disposing)
  747. {
  748. if (!disposing || _disposed)
  749. {
  750. return;
  751. }
  752. _reg1.Dispose();
  753. base.Dispose(disposing);
  754. }
  755. }
  756. private sealed class Linked2CancellationTokenSource : CancellationTokenSource
  757. {
  758. private readonly CancellationTokenRegistration _reg1;
  759. private readonly CancellationTokenRegistration _reg2;
  760. internal Linked2CancellationTokenSource(CancellationToken token1, CancellationToken token2)
  761. {
  762. _reg1 = token1.UnsafeRegister(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
  763. _reg2 = token2.UnsafeRegister(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
  764. }
  765. protected override void Dispose(bool disposing)
  766. {
  767. if (!disposing || _disposed)
  768. {
  769. return;
  770. }
  771. _reg1.Dispose();
  772. _reg2.Dispose();
  773. base.Dispose(disposing);
  774. }
  775. }
  776. private sealed class LinkedNCancellationTokenSource : CancellationTokenSource
  777. {
  778. internal static readonly Action<object> s_linkedTokenCancelDelegate =
  779. s => ((CancellationTokenSource)s).NotifyCancellation(throwOnFirstException: false); // skip ThrowIfDisposed() check in Cancel()
  780. private CancellationTokenRegistration[] _linkingRegistrations;
  781. internal LinkedNCancellationTokenSource(params CancellationToken[] tokens)
  782. {
  783. _linkingRegistrations = new CancellationTokenRegistration[tokens.Length];
  784. for (int i = 0; i < tokens.Length; i++)
  785. {
  786. if (tokens[i].CanBeCanceled)
  787. {
  788. _linkingRegistrations[i] = tokens[i].UnsafeRegister(s_linkedTokenCancelDelegate, this);
  789. }
  790. // Empty slots in the array will be default(CancellationTokenRegistration), which are nops to Dispose.
  791. // Based on usage patterns, such occurrences should also be rare, such that it's not worth resizing
  792. // the array and incurring the related costs.
  793. }
  794. }
  795. protected override void Dispose(bool disposing)
  796. {
  797. if (!disposing || _disposed)
  798. {
  799. return;
  800. }
  801. CancellationTokenRegistration[] linkingRegistrations = _linkingRegistrations;
  802. if (linkingRegistrations != null)
  803. {
  804. _linkingRegistrations = null; // release for GC once we're done enumerating
  805. for (int i = 0; i < linkingRegistrations.Length; i++)
  806. {
  807. linkingRegistrations[i].Dispose();
  808. }
  809. }
  810. base.Dispose(disposing);
  811. }
  812. }
  813. internal sealed class CallbackPartition
  814. {
  815. /// <summary>The associated source that owns this partition.</summary>
  816. public readonly CancellationTokenSource Source;
  817. /// <summary>Lock that protects all state in the partition.</summary>
  818. public SpinLock Lock = new SpinLock(enableThreadOwnerTracking: false); // mutable struct; do not make this readonly
  819. /// <summary>Doubly-linked list of callbacks registered with the partition. Callbacks are removed during unregistration and as they're invoked.</summary>
  820. public CallbackNode Callbacks;
  821. /// <summary>Singly-linked list of free nodes that can be used for subsequent callback registrations.</summary>
  822. public CallbackNode FreeNodeList;
  823. /// <summary>Every callback is assigned a unique, never-reused ID. This defines the next available ID.</summary>
  824. public long NextAvailableId = 1; // avoid using 0, as that's the default long value and used to represent an empty node
  825. public CallbackPartition(CancellationTokenSource source)
  826. {
  827. Debug.Assert(source != null, "Expected non-null source");
  828. Source = source;
  829. }
  830. internal bool Unregister(long id, CallbackNode node)
  831. {
  832. Debug.Assert(id != 0, "Expected non-zero id");
  833. Debug.Assert(node != null, "Expected non-null node");
  834. bool lockTaken = false;
  835. Lock.Enter(ref lockTaken);
  836. try
  837. {
  838. if (node.Id != id)
  839. {
  840. // Either:
  841. // - The callback is currently or has already been invoked, in which case node.Id
  842. // will no longer equal the assigned id, as it will have transitioned to 0.
  843. // - The registration was already disposed of, in which case node.Id will similarly
  844. // no longer equal the assigned id, as it will have transitioned to 0 and potentially
  845. // then to another (larger) value when reused for a new registration.
  846. // In either case, there's nothing to unregister.
  847. return false;
  848. }
  849. // The registration must still be in the callbacks list. Remove it.
  850. if (Callbacks == node)
  851. {
  852. Debug.Assert(node.Prev == null);
  853. Callbacks = node.Next;
  854. }
  855. else
  856. {
  857. Debug.Assert(node.Prev != null);
  858. node.Prev.Next = node.Next;
  859. }
  860. if (node.Next != null)
  861. {
  862. node.Next.Prev = node.Prev;
  863. }
  864. // Clear out the now unused node and put it on the singly-linked free list.
  865. // The only field we don't clear out is the associated Partition, as that's fixed
  866. // throughout the nodes lifetime, regardless of how many times its reused by
  867. // the same partition (it's never used on a different partition).
  868. node.Id = 0;
  869. node.Callback = null;
  870. node.CallbackState = null;
  871. node.ExecutionContext = null;
  872. node.SynchronizationContext = null;
  873. node.Prev = null;
  874. node.Next = FreeNodeList;
  875. FreeNodeList = node;
  876. return true;
  877. }
  878. finally
  879. {
  880. Lock.Exit(useMemoryBarrier: false); // no check on lockTaken needed without thread aborts
  881. }
  882. }
  883. }
  884. /// <summary>All of the state associated a registered callback, in a node that's part of a linked list of registered callbacks.</summary>
  885. internal sealed class CallbackNode
  886. {
  887. public readonly CallbackPartition Partition;
  888. public CallbackNode Prev;
  889. public CallbackNode Next;
  890. public long Id;
  891. public Action<object> Callback;
  892. public object CallbackState;
  893. public ExecutionContext ExecutionContext;
  894. public SynchronizationContext SynchronizationContext;
  895. public CallbackNode(CallbackPartition partition)
  896. {
  897. Debug.Assert(partition != null, "Expected non-null partition");
  898. Partition = partition;
  899. }
  900. public void ExecuteCallback()
  901. {
  902. ExecutionContext context = ExecutionContext;
  903. if (context != null)
  904. {
  905. ExecutionContext.RunInternal(context, s =>
  906. {
  907. CallbackNode n = (CallbackNode)s;
  908. n.Callback(n.CallbackState);
  909. }, this);
  910. }
  911. else
  912. {
  913. Callback(CallbackState);
  914. }
  915. }
  916. }
  917. }
  918. }