1 #pragma warning disable 0420
4 // Copyright (c) Microsoft Corporation. All rights reserved.
8 // <OWNER>[....]</OWNER>
9 ////////////////////////////////////////////////////////////////////////////////
12 using System.Security;
13 using System.Collections.Generic;
14 using System.Runtime.InteropServices;
15 using System.Security.Permissions;
16 using System.Diagnostics.Contracts;
19 namespace System.Threading
22 /// Signals to a <see cref="System.Threading.CancellationToken"/> that it should be canceled.
26 /// <see cref="T:System.Threading.CancellationTokenSource"/> is used to instantiate a <see
27 /// cref="T:System.Threading.CancellationToken"/>
28 /// (via the source's <see cref="System.Threading.CancellationTokenSource.Token">Token</see> property)
29 /// that can be handed to operations that wish to be notified of cancellation or that can be used to
30 /// register asynchronous operations for cancellation. That token may have cancellation requested by
31 /// calling to the source's <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see>
35 /// All members of this class, except <see cref="Dispose">Dispose</see>, are thread-safe and may be used
36 /// concurrently from multiple threads.
40 [HostProtection(Synchronization = true, ExternalThreading = true)]
42 public class CancellationTokenSource : IDisposable
44 //static sources that can be used as the backing source for 'fixed' CancellationTokens that never change state.
45 private static readonly CancellationTokenSource _staticSource_Set = new CancellationTokenSource(true);
46 private static readonly CancellationTokenSource _staticSource_NotCancelable = new CancellationTokenSource(false);
48 //Note: the callback lists array is only created on first registration.
49 // the actual callback lists are only created on demand.
50 // Storing a registered callback costs around >60bytes, hence some overhead for the lists array is OK
51 // At most 24 lists seems reasonable, and caps the cost of the listsArray to 96bytes(32-bit,24-way) or 192bytes(64-bit,24-way).
52 private static readonly int s_nLists = (PlatformHelper.ProcessorCount > 24) ? 24 : PlatformHelper.ProcessorCount;
54 private volatile ManualResetEvent m_kernelEvent; //lazily initialized if required.
56 private volatile SparselyPopulatedArray<CancellationCallbackInfo>[] m_registeredCallbacksLists;
58 // legal values for m_state
59 private const int CANNOT_BE_CANCELED = 0;
60 private const int NOT_CANCELED = 1;
61 private const int NOTIFYING = 2;
62 private const int NOTIFYINGCOMPLETE = 3;
64 //m_state uses the pattern "volatile int32 reads, with cmpxch writes" which is safe for updates and cannot suffer torn reads.
65 private volatile int m_state;
68 /// The ID of the thread currently executing the main body of CTS.Cancel()
69 /// this helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback.
70 /// This is updated as we move between the main thread calling cts.Cancel() and any syncContexts that are used to
71 /// actually run the callbacks.
72 private volatile int m_threadIDExecutingCallbacks = -1;
74 private bool m_disposed;
76 private CancellationTokenRegistration [] m_linkingRegistrations; //lazily initialized if required.
78 private static readonly Action<object> s_LinkedTokenCancelDelegate = new Action<object>(LinkedTokenCancelDelegate);
80 // we track the running callback to assist ctr.Dispose() to wait for the target callback to complete.
81 private volatile CancellationCallbackInfo m_executingCallback;
83 // provided for CancelAfter and timer-related constructors
84 private volatile Timer m_timer;
86 private static void LinkedTokenCancelDelegate(object source)
88 CancellationTokenSource cts = source as CancellationTokenSource;
89 Contract.Assert(source != null);
93 // ----------------------
94 // ** public properties
97 /// Gets whether cancellation has been requested for this <see
98 /// cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see>.
100 /// <value>Whether cancellation has been requested for this <see
101 /// cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see>.</value>
104 /// This property indicates whether cancellation has been requested for this token source, such as
105 /// due to a call to its
106 /// <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see> method.
109 /// If this property returns true, it only guarantees that cancellation has been requested. It does not
110 /// guarantee that every handler registered with the corresponding token has finished executing, nor
111 /// that cancellation requests have finished propagating to all registered handlers. Additional
112 /// synchronization may be required, particularly in situations where related objects are being
113 /// canceled concurrently.
116 public bool IsCancellationRequested
118 get { return m_state >= NOTIFYING; }
122 /// A simple helper to determine whether cancellation has finished.
124 internal bool IsCancellationCompleted
126 get { return m_state == NOTIFYINGCOMPLETE; }
130 /// A simple helper to determine whether disposal has occured.
132 internal bool IsDisposed
134 get { return m_disposed; }
138 /// The ID of the thread that is running callbacks.
140 internal int ThreadIDExecutingCallbacks
142 set { m_threadIDExecutingCallbacks = value; }
143 get { return m_threadIDExecutingCallbacks; }
147 /// Gets the <see cref="System.Threading.CancellationToken">CancellationToken</see>
148 /// associated with this <see cref="CancellationTokenSource"/>.
150 /// <value>The <see cref="System.Threading.CancellationToken">CancellationToken</see>
151 /// associated with this <see cref="CancellationTokenSource"/>.</value>
152 /// <exception cref="T:System.ObjectDisposedException">The token source has been
153 /// disposed.</exception>
154 public CancellationToken Token
159 return new CancellationToken(this);
163 // ----------------------
164 // ** internal and private properties.
169 internal bool CanBeCanceled
171 get { return m_state != CANNOT_BE_CANCELED; }
177 internal WaitHandle WaitHandle
183 // fast path if already allocated.
184 if (m_kernelEvent != null)
185 return m_kernelEvent;
187 // lazy-init the mre.
188 ManualResetEvent mre = new ManualResetEvent(false);
189 if (Interlocked.CompareExchange(ref m_kernelEvent, mre, null) != null)
191 ((IDisposable)mre).Dispose();
194 // There is a ---- between checking IsCancellationRequested and setting the event.
195 // However, at this point, the kernel object definitely exists and the cases are:
196 // 1. if IsCancellationRequested = true, then we will call Set()
197 // 2. if IsCancellationRequested = false, then NotifyCancellation will see that the event exists, and will call Set().
198 if (IsCancellationRequested)
201 return m_kernelEvent;
207 /// The currently executing callback
209 internal CancellationCallbackInfo ExecutingCallback
211 get { return m_executingCallback; }
216 /// Used by the dev unit tests to check the number of outstanding registrations.
217 /// They use private reflection to gain access. Because this would be dead retail
218 /// code, however, it is ifdef'd out to work only in debug builds.
220 private int CallbackCount
224 SparselyPopulatedArray<CancellationCallbackInfo>[] callbackLists = m_registeredCallbacksLists;
225 if (callbackLists == null)
229 foreach(SparselyPopulatedArray<CancellationCallbackInfo> sparseArray in callbackLists)
231 if(sparseArray != null)
233 SparselyPopulatedArrayFragment<CancellationCallbackInfo> currCallbacks = sparseArray.Head;
234 while (currCallbacks != null)
236 for (int i = 0; i < currCallbacks.Length; i++)
237 if (currCallbacks[i] != null)
240 currCallbacks = currCallbacks.Next;
249 // ** Public Constructors
252 /// Initializes the <see cref="T:System.Threading.CancellationTokenSource"/>.
254 public CancellationTokenSource()
256 m_state = NOT_CANCELED;
259 // ** Private constructors for static sources.
260 // set=false ==> cannot be canceled.
261 // set=true ==> is canceled.
262 private CancellationTokenSource(bool set)
264 m_state = set ? NOTIFYINGCOMPLETE : CANNOT_BE_CANCELED;
268 /// Constructs a <see cref="T:System.Threading.CancellationTokenSource"/> that will be canceled after a specified time span.
270 /// <param name="delay">The time span to wait before canceling this <see cref="T:System.Threading.CancellationTokenSource"/></param>
271 /// <exception cref="T:System.ArgumentOutOfRangeException">
272 /// The exception that is thrown when <paramref name="delay"/> is less than -1 or greater than Int32.MaxValue.
276 /// The countdown for the delay starts during the call to the constructor. When the delay expires,
277 /// the constructed <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has
278 /// not been canceled already.
281 /// Subsequent calls to CancelAfter will reset the delay for the constructed
282 /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been
283 /// canceled already.
286 public CancellationTokenSource(TimeSpan delay)
288 long totalMilliseconds = (long)delay.TotalMilliseconds;
289 if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
291 throw new ArgumentOutOfRangeException("delay");
294 InitializeWithTimer((int)totalMilliseconds);
298 /// Constructs a <see cref="T:System.Threading.CancellationTokenSource"/> that will be canceled after a specified time span.
300 /// <param name="millisecondsDelay">The time span to wait before canceling this <see cref="T:System.Threading.CancellationTokenSource"/></param>
301 /// <exception cref="T:System.ArgumentOutOfRangeException">
302 /// The exception that is thrown when <paramref name="millisecondsDelay"/> is less than -1.
306 /// The countdown for the millisecondsDelay starts during the call to the constructor. When the millisecondsDelay expires,
307 /// the constructed <see cref="T:System.Threading.CancellationTokenSource"/> is canceled (if it has
308 /// not been canceled already).
311 /// Subsequent calls to CancelAfter will reset the millisecondsDelay for the constructed
312 /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been
313 /// canceled already.
316 public CancellationTokenSource(Int32 millisecondsDelay)
318 if (millisecondsDelay < -1)
320 throw new ArgumentOutOfRangeException("millisecondsDelay");
323 InitializeWithTimer(millisecondsDelay);
326 // Common initialization logic when constructing a CTS with a delay parameter
327 private void InitializeWithTimer(Int32 millisecondsDelay)
329 m_state = NOT_CANCELED;
330 m_timer = new Timer(s_timerCallback, this, millisecondsDelay, -1);
336 /// Communicates a request for cancellation.
340 /// The associated <see cref="T:System.Threading.CancellationToken" /> will be
341 /// notified of the cancellation and will transition to a state where
342 /// <see cref="System.Threading.CancellationToken.IsCancellationRequested">IsCancellationRequested</see> returns true.
343 /// Any callbacks or cancelable operations
344 /// registered with the <see cref="T:System.Threading.CancellationToken"/> will be executed.
347 /// Cancelable operations and callbacks registered with the token should not throw exceptions.
348 /// However, this overload of Cancel will aggregate any exceptions thrown into a <see cref="System.AggregateException"/>,
349 /// such that one callback throwing an exception will not prevent other registered callbacks from being executed.
352 /// The <see cref="T:System.Threading.ExecutionContext"/> that was captured when each callback was registered
353 /// will be reestablished when the callback is invoked.
356 /// <exception cref="T:System.AggregateException">An aggregate exception containing all the exceptions thrown
357 /// by the registered callbacks on the associated <see cref="T:System.Threading.CancellationToken"/>.</exception>
358 /// <exception cref="T:System.ObjectDisposedException">This <see
359 /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.</exception>
366 /// Communicates a request for cancellation.
370 /// The associated <see cref="T:System.Threading.CancellationToken" /> will be
371 /// notified of the cancellation and will transition to a state where
372 /// <see cref="System.Threading.CancellationToken.IsCancellationRequested">IsCancellationRequested</see> returns true.
373 /// Any callbacks or cancelable operations
374 /// registered with the <see cref="T:System.Threading.CancellationToken"/> will be executed.
377 /// Cancelable operations and callbacks registered with the token should not throw exceptions.
378 /// If <paramref name="throwOnFirstException"/> is true, an exception will immediately propagate out of the
379 /// call to Cancel, preventing the remaining callbacks and cancelable operations from being processed.
380 /// If <paramref name="throwOnFirstException"/> is false, this overload will aggregate any
381 /// exceptions thrown into a <see cref="System.AggregateException"/>,
382 /// such that one callback throwing an exception will not prevent other registered callbacks from being executed.
385 /// The <see cref="T:System.Threading.ExecutionContext"/> that was captured when each callback was registered
386 /// will be reestablished when the callback is invoked.
389 /// <param name="throwOnFirstException">Specifies whether exceptions should immediately propagate.</param>
390 /// <exception cref="T:System.AggregateException">An aggregate exception containing all the exceptions thrown
391 /// by the registered callbacks on the associated <see cref="T:System.Threading.CancellationToken"/>.</exception>
392 /// <exception cref="T:System.ObjectDisposedException">This <see
393 /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.</exception>
394 public void Cancel(bool throwOnFirstException)
397 NotifyCancellation(throwOnFirstException);
401 /// Schedules a Cancel operation on this <see cref="T:System.Threading.CancellationTokenSource"/>.
403 /// <param name="delay">The time span to wait before canceling this <see
404 /// cref="T:System.Threading.CancellationTokenSource"/>.
406 /// <exception cref="T:System.ObjectDisposedException">The exception thrown when this <see
407 /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.
409 /// <exception cref="T:System.ArgumentOutOfRangeException">
410 /// The exception thrown when <paramref name="delay"/> is less than -1 or
411 /// greater than Int32.MaxValue.
415 /// The countdown for the delay starts during this call. When the delay expires,
416 /// this <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has
417 /// not been canceled already.
420 /// Subsequent calls to CancelAfter will reset the delay for this
421 /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been
422 /// canceled already.
425 public void CancelAfter(TimeSpan delay)
427 long totalMilliseconds = (long)delay.TotalMilliseconds;
428 if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue)
430 throw new ArgumentOutOfRangeException("delay");
433 CancelAfter((int)totalMilliseconds);
437 /// Schedules a Cancel operation on this <see cref="T:System.Threading.CancellationTokenSource"/>.
439 /// <param name="millisecondsDelay">The time span to wait before canceling this <see
440 /// cref="T:System.Threading.CancellationTokenSource"/>.
442 /// <exception cref="T:System.ObjectDisposedException">The exception thrown when this <see
443 /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.
445 /// <exception cref="T:System.ArgumentOutOfRangeException">
446 /// The exception thrown when <paramref name="millisecondsDelay"/> is less than -1.
450 /// The countdown for the millisecondsDelay starts during this call. When the millisecondsDelay expires,
451 /// this <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has
452 /// not been canceled already.
455 /// Subsequent calls to CancelAfter will reset the millisecondsDelay for this
456 /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been
457 /// canceled already.
460 public void CancelAfter(Int32 millisecondsDelay)
464 if (millisecondsDelay < -1)
466 throw new ArgumentOutOfRangeException("millisecondsDelay");
469 if (IsCancellationRequested) return;
471 // There is a race condition here as a Cancel could occur between the check of
472 // IsCancellationRequested and the creation of the timer. This is benign; in the
473 // worst case, a timer will be created that has no effect when it expires.
475 // Also, if Dispose() is called right here (after ThrowIfDisposed(), before timer
476 // creation), it would result in a leaked Timer object (at least until the timer
477 // expired and Disposed itself). But this would be considered bad behavior, as
478 // Dispose() is not thread-safe and should not be called concurrently with CancelAfter().
482 // Lazily initialize the timer in a thread-safe fashion.
483 // Initially set to "never go off" because we don't want to take a
484 // chance on a timer "losing" the initialization ---- and then
485 // cancelling the token before it (the timer) can be disposed.
486 Timer newTimer = new Timer(s_timerCallback, this, -1, -1);
487 if (Interlocked.CompareExchange(ref m_timer, newTimer, null) != null)
489 // We lost the ---- to initialize the timer. Dispose the new timer.
495 // It is possible that m_timer has already been disposed, so we must do
496 // the following in a try/catch block.
499 m_timer.Change(millisecondsDelay, -1);
501 catch (ObjectDisposedException)
503 // Just eat the exception. There is no other way to tell that
504 // the timer has been disposed, and even if there were, there
505 // would not be a good way to deal with the observe/dispose
511 private static readonly TimerCallback s_timerCallback = new TimerCallback(TimerCallbackLogic);
513 // Common logic for a timer delegate
514 private static void TimerCallbackLogic(object obj)
516 CancellationTokenSource cts = (CancellationTokenSource)obj;
518 // Cancel the source; handle a race condition with cts.Dispose()
521 // There is a small window for a race condition where a cts.Dispose can sneak
522 // in right here. I'll wrap the cts.Cancel() in a try/catch to proof us
523 // against this ----.
526 cts.Cancel(); // will take care of disposing of m_timer
528 catch (ObjectDisposedException)
530 // If the ODE was not due to the target cts being disposed, then propagate the ODE.
531 if (!cts.IsDisposed) throw;
537 /// Releases the resources used by this <see cref="T:System.Threading.CancellationTokenSource" />.
540 /// This method is not thread-safe for any other concurrent calls.
542 public void Dispose()
545 GC.SuppressFinalize(this);
549 /// Releases the unmanaged resources used by the <see cref="T:System.Threading.CancellationTokenSource" /> class and optionally releases the managed resources.
551 /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
552 protected virtual void Dispose(bool disposing)
554 // There is nothing to do if disposing=false because the CancellationTokenSource holds no unmanaged resources.
558 //NOTE: We specifically tolerate that a callback can be deregistered
559 // after the CTS has been disposed and/or concurrently with cts.Dispose().
560 // This is safe without locks because the reg.Dispose() only
561 // mutates a sparseArrayFragment and then reads from properties of the CTS that are not
562 // invalidated by cts.Dispose().
564 // We also tolerate that a callback can be registered after the CTS has been
565 // disposed. This is safe without locks because InternalRegister is tolerant
566 // of m_registeredCallbacksLists becoming null during its execution. However,
567 // we run the acceptable risk of m_registeredCallbacksLists getting reinitialized
568 // to non-null if there is a ---- between Dispose and Register, in which case this
569 // instance may unnecessarily hold onto a registered callback. But that's no worse
570 // than if Dispose wasn't safe to use concurrently, as Dispose would never be called,
571 // and thus no handlers would be dropped.
576 if (m_timer != null) m_timer.Dispose();
578 var linkingRegistrations = m_linkingRegistrations;
579 if (linkingRegistrations != null)
581 m_linkingRegistrations = null; // free for GC once we're done enumerating
582 for (int i = 0; i < linkingRegistrations.Length; i++)
584 linkingRegistrations[i].Dispose();
588 // registered callbacks are now either complete or will never run, due to guarantees made by ctr.Dispose()
589 // so we can now perform main disposal work without risk of linking callbacks trying to use this CTS.
591 m_registeredCallbacksLists = null; // free for GC.
595 // .NET version has a race on m_kernelEvent which it's not easy to
596 // trigger on .net probably due to much faster Close implementation
597 // but on Mono this can happen quite easily.
599 // First race was between Dispose and NotifyCancellation where m_kernelEvent
600 // can be nulled/Closed and Set at same time.
602 // Second race was between concurrent Dispose calls.
604 // Third race is between Dispose and WaitHandle propery but that should
605 // be handled by user.
607 var ke = m_kernelEvent;
610 m_kernelEvent = null;
614 if (m_kernelEvent != null)
616 m_kernelEvent.Close(); // the critical cleanup to release an OS handle
617 m_kernelEvent = null; // free for GC.
625 // -- Internal methods.
628 /// Throws an exception if the source has been disposed.
630 internal void ThrowIfDisposed()
633 ThrowObjectDisposedException();
636 // separation enables inlining of ThrowIfDisposed
637 private static void ThrowObjectDisposedException()
639 throw new ObjectDisposedException(null, Environment.GetResourceString("CancellationTokenSource_Disposed"));
643 /// InternalGetStaticSource()
645 /// <param name="set">Whether the source should be set.</param>
646 /// <returns>A static source to be shared among multiple tokens.</returns>
647 internal static CancellationTokenSource InternalGetStaticSource(bool set)
649 return set ? _staticSource_Set : _staticSource_NotCancelable;
653 /// Registers a callback object. If cancellation has already occurred, the
654 /// callback will have been run by the time this method returns.
656 internal CancellationTokenRegistration InternalRegister(
657 Action<object> callback, object stateForCallback, SynchronizationContext targetSyncContext, ExecutionContext executionContext)
659 if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource)
664 // the CancellationToken has already checked that the token is cancelable before calling this method.
665 Contract.Assert(CanBeCanceled, "Cannot register for uncancelable token src");
667 // if not canceled, register the event handlers
668 // if canceled already, run the callback synchronously
669 // Apart from the semantics of late-enlistment, this also ensures that during ExecuteCallbackHandlers() there
670 // will be no mutation of the _registeredCallbacks list
672 if (!IsCancellationRequested)
674 // In order to enable code to not leak too many handlers, we allow Dispose to be called concurrently
675 // with Register. While this is not a recommended practice, consumers can and do use it this way.
676 // We don't make any guarantees about whether the CTS will hold onto the supplied callback
677 // if the CTS has already been disposed when the callback is registered, but we try not to
678 // while at the same time not paying any non-negligible overhead. The simple compromise
679 // is to check whether we're disposed (not volatile), and if we see we are, to return an empty
680 // registration, just as if CanBeCanceled was false for the check made in CancellationToken.Register.
681 // If there's a ---- and m_disposed is false even though it's been disposed, or if the disposal request
682 // comes in after this line, we simply run the minor risk of having m_registeredCallbacksLists reinitialized
683 // (after it was cleared to null during Dispose).
685 if (m_disposed && !AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource)
686 return new CancellationTokenRegistration();
688 int myIndex = Thread.CurrentThread.ManagedThreadId % s_nLists;
690 CancellationCallbackInfo callbackInfo = new CancellationCallbackInfo(callback, stateForCallback, targetSyncContext, executionContext, this);
692 //allocate the callback list array
693 var registeredCallbacksLists = m_registeredCallbacksLists;
694 if (registeredCallbacksLists == null)
696 SparselyPopulatedArray<CancellationCallbackInfo>[] list = new SparselyPopulatedArray<CancellationCallbackInfo>[s_nLists];
697 registeredCallbacksLists = Interlocked.CompareExchange(ref m_registeredCallbacksLists, list, null);
698 if (registeredCallbacksLists == null) registeredCallbacksLists = list;
701 //allocate the actual lists on-demand to save mem in low-use situations, and to avoid false-sharing.
702 var callbacks = Volatile.Read<SparselyPopulatedArray<CancellationCallbackInfo>>(ref registeredCallbacksLists[myIndex]);
703 if (callbacks == null)
705 SparselyPopulatedArray<CancellationCallbackInfo> callBackArray = new SparselyPopulatedArray<CancellationCallbackInfo>(4);
706 Interlocked.CompareExchange(ref (registeredCallbacksLists[myIndex]), callBackArray, null);
707 callbacks = registeredCallbacksLists[myIndex];
710 // Now add the registration to the list.
711 SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> addInfo = callbacks.Add(callbackInfo);
712 CancellationTokenRegistration registration = new CancellationTokenRegistration(callbackInfo, addInfo);
714 if (!IsCancellationRequested)
717 // If a cancellation has since come in, we will try to undo the registration and run the callback ourselves.
718 // (this avoids leaving the callback orphaned)
719 bool deregisterOccurred = registration.TryDeregister();
721 if (!deregisterOccurred)
723 // The thread that is running Cancel() snagged our callback for execution.
724 // So we don't need to run it, but we do return the registration so that
725 // ctr.Dispose() will wait for callback completion.
730 // If cancellation already occurred, we run the callback on this thread and return an empty registration.
731 callback(stateForCallback);
732 return new CancellationTokenRegistration();
738 private void NotifyCancellation(bool throwOnFirstException)
740 // fast-path test to check if Notify has been called previously
741 if (IsCancellationRequested)
744 // If we're the first to signal cancellation, do the main extra work.
745 if (Interlocked.CompareExchange(ref m_state, NOTIFYING, NOT_CANCELED) == NOT_CANCELED)
747 // Dispose of the timer, if any
748 Timer timer = m_timer;
749 if(timer != null) timer.Dispose();
751 //record the threadID being used for running the callbacks.
752 ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
754 //If the kernel event is null at this point, it will be set during lazy construction.
756 var ke = m_kernelEvent;
759 ke.Set(); // update the MRE value.
760 } catch (ObjectDisposedException) {
762 // Rethrow only when we are not in race with Dispose
764 if (m_kernelEvent != null)
769 if (m_kernelEvent != null)
770 m_kernelEvent.Set(); // update the MRE value.
773 // - late enlisters to the Canceled event will have their callbacks called immediately in the Register() methods.
774 // - Callbacks are not called inside a lock.
775 // - After transition, no more delegates will be added to the
776 // - list of handlers, and hence it can be consumed and cleared at leisure by ExecuteCallbackHandlers.
777 ExecuteCallbackHandlers(throwOnFirstException);
778 Contract.Assert(IsCancellationCompleted, "Expected cancellation to have finished");
783 /// Invoke the Canceled event.
786 /// The handlers are invoked synchronously in LIFO order.
788 private void ExecuteCallbackHandlers(bool throwOnFirstException)
790 Contract.Assert(IsCancellationRequested, "ExecuteCallbackHandlers should only be called after setting IsCancellationRequested->true");
791 Contract.Assert(ThreadIDExecutingCallbacks != -1, "ThreadIDExecutingCallbacks should have been set.");
793 // Design decision: call the delegates in LIFO order so that callbacks fire 'deepest first'.
794 // This is intended to help with nesting scenarios so that child enlisters cancel before their parents.
795 List<Exception> exceptionList = null;
796 SparselyPopulatedArray<CancellationCallbackInfo>[] callbackLists = m_registeredCallbacksLists;
798 // If there are no callbacks to run, we can safely exit. Any ----s to lazy initialize it
799 // will see IsCancellationRequested and will then run the callback themselves.
800 if (callbackLists == null)
802 Interlocked.Exchange(ref m_state, NOTIFYINGCOMPLETE);
808 for (int index = 0; index < callbackLists.Length; index++)
810 SparselyPopulatedArray<CancellationCallbackInfo> list = Volatile.Read<SparselyPopulatedArray<CancellationCallbackInfo>>(ref callbackLists[index]);
813 SparselyPopulatedArrayFragment<CancellationCallbackInfo> currArrayFragment = list.Tail;
815 while (currArrayFragment != null)
817 for (int i = currArrayFragment.Length - 1; i >= 0; i--)
819 // 1a. publish the indended callback, to ensure ctr.Dipose can tell if a wait is necessary.
820 // 1b. transition to the target syncContext and continue there..
821 // On the target SyncContext.
822 // 2. actually remove the callback
823 // 3. execute the callback
824 // re:#2 we do the remove on the syncCtx so that we can be sure we have control of the syncCtx before
825 // grabbing the callback. This prevents a deadlock if ctr.Dispose() might run on the syncCtx too.
826 m_executingCallback = currArrayFragment[i];
827 if (m_executingCallback != null)
829 //Transition to the target [....] context (if necessary), and continue our work there.
830 CancellationCallbackCoreWorkArguments args = new CancellationCallbackCoreWorkArguments(currArrayFragment, i);
832 // marshal exceptions: either aggregate or perform an immediate rethrow
833 // We assume that syncCtx.Send() has forwarded on user exceptions when appropriate.
836 if (m_executingCallback.TargetSyncContext != null)
839 m_executingCallback.TargetSyncContext.Send(CancellationCallbackCoreWork_OnSyncContext, args);
840 // CancellationCallbackCoreWork_OnSyncContext may have altered ThreadIDExecutingCallbacks, so reset it.
841 ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
845 CancellationCallbackCoreWork(args);
850 if (throwOnFirstException)
853 // Otherwise, log it and proceed.
854 if(exceptionList == null)
855 exceptionList = new List<Exception>();
856 exceptionList.Add(ex);
861 currArrayFragment = currArrayFragment.Prev;
868 m_state = NOTIFYINGCOMPLETE;
869 m_executingCallback = null;
870 Thread.MemoryBarrier(); // for safety, prevent reorderings crossing this point and seeing inconsistent state.
873 if (exceptionList != null)
875 Contract.Assert(exceptionList.Count > 0, "Expected exception count > 0");
876 throw new AggregateException(exceptionList);
880 // The main callback work that executes on the target synchronization context
881 private void CancellationCallbackCoreWork_OnSyncContext(object obj)
883 CancellationCallbackCoreWork((CancellationCallbackCoreWorkArguments)obj);
886 private void CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
888 // remove the intended callback..and ensure that it worked.
889 // otherwise the callback has disappeared in the interim and we can immediately return.
890 CancellationCallbackInfo callback = args.m_currArrayFragment.SafeAtomicRemove(args.m_currArrayIndex, m_executingCallback);
891 if (callback == m_executingCallback)
893 if (callback.TargetExecutionContext != null)
895 // we are running via a custom [....] context, so update the executing threadID
896 callback.CancellationTokenSource.ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId;
898 callback.ExecuteCallback();
904 /// Creates a <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that will be in the canceled state
905 /// when any of the source tokens are in the canceled state.
907 /// <param name="token1">The first <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to observe.</param>
908 /// <param name="token2">The second <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to observe.</param>
909 /// <returns>A <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that is linked
910 /// to the source tokens.</returns>
911 public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2)
913 CancellationTokenSource linkedTokenSource = new CancellationTokenSource();
915 bool token2CanBeCanceled = token2.CanBeCanceled;
917 if( token1.CanBeCanceled )
919 linkedTokenSource.m_linkingRegistrations = new CancellationTokenRegistration[token2CanBeCanceled ? 2 : 1]; // there will be at least 1 and at most 2 linkings
920 linkedTokenSource.m_linkingRegistrations[0] = token1.InternalRegisterWithoutEC(s_LinkedTokenCancelDelegate, linkedTokenSource);
923 if( token2CanBeCanceled )
926 if( linkedTokenSource.m_linkingRegistrations == null )
928 linkedTokenSource.m_linkingRegistrations = new CancellationTokenRegistration[1]; // this will be the only linking
931 linkedTokenSource.m_linkingRegistrations[index] = token2.InternalRegisterWithoutEC(s_LinkedTokenCancelDelegate, linkedTokenSource);
934 return linkedTokenSource;
938 /// Creates a <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that will be in the canceled state
939 /// when any of the source tokens are in the canceled state.
941 /// <param name="tokens">The <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances to observe.</param>
942 /// <returns>A <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that is linked
943 /// to the source tokens.</returns>
944 /// <exception cref="T:System.ArgumentNullException"><paramref name="tokens"/> is null.</exception>
945 public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens)
948 throw new ArgumentNullException("tokens");
950 if (tokens.Length == 0)
951 throw new ArgumentException(Environment.GetResourceString("CancellationToken_CreateLinkedToken_TokensIsEmpty"));
953 // a defensive copy is not required as the array has value-items that have only a single IntPtr field,
954 // hence each item cannot be null itself, and reads of the payloads cannot be torn.
955 Contract.EndContractBlock();
957 CancellationTokenSource linkedTokenSource = new CancellationTokenSource();
958 linkedTokenSource.m_linkingRegistrations = new CancellationTokenRegistration[tokens.Length];
960 for (int i = 0; i < tokens.Length; i++)
962 if (tokens[i].CanBeCanceled)
964 linkedTokenSource.m_linkingRegistrations[i] = tokens[i].InternalRegisterWithoutEC(s_LinkedTokenCancelDelegate, linkedTokenSource);
966 // Empty slots in the array will be default(CancellationTokenRegistration), which are nops to Dispose.
967 // Based on usage patterns, such occurrences should also be rare, such that it's not worth resizing
968 // the array and incurring the related costs.
971 return linkedTokenSource;
975 // Wait for a single callback to complete (or, more specifically, to not be running).
976 // It is ok to call this method if the callback has already finished.
977 // Calling this method before the target callback has been selected for execution would be an error.
978 internal void WaitForCallbackToComplete(CancellationCallbackInfo callbackInfo)
980 SpinWait sw = new SpinWait();
981 while (ExecutingCallback == callbackInfo)
983 sw.SpinOnce(); //spin as we assume callback execution is fast and that this situation is rare.
988 // ----------------------------------------------------------
989 // -- CancellationCallbackCoreWorkArguments --
990 // ----------------------------------------------------------
991 // Helper struct for passing data to the target [....] context
992 internal struct CancellationCallbackCoreWorkArguments
994 internal SparselyPopulatedArrayFragment<CancellationCallbackInfo> m_currArrayFragment;
995 internal int m_currArrayIndex;
997 public CancellationCallbackCoreWorkArguments(SparselyPopulatedArrayFragment<CancellationCallbackInfo> currArrayFragment, int currArrayIndex)
999 m_currArrayFragment = currArrayFragment;
1000 m_currArrayIndex = currArrayIndex;
1004 // ----------------------------------------------------------
1005 // -- CancellationCallbackInfo --
1006 // ----------------------------------------------------------
1009 /// A helper class for collating the various bits of information required to execute
1010 /// cancellation callbacks.
1012 internal class CancellationCallbackInfo
1014 internal readonly Action<object> Callback;
1015 internal readonly object StateForCallback;
1016 internal readonly SynchronizationContext TargetSyncContext;
1017 internal readonly ExecutionContext TargetExecutionContext;
1018 internal readonly CancellationTokenSource CancellationTokenSource;
1020 internal CancellationCallbackInfo(
1021 Action<object> callback, object stateForCallback, SynchronizationContext targetSyncContext, ExecutionContext targetExecutionContext,
1022 CancellationTokenSource cancellationTokenSource)
1024 Callback = callback;
1025 StateForCallback = stateForCallback;
1026 TargetSyncContext = targetSyncContext;
1027 TargetExecutionContext = targetExecutionContext;
1028 CancellationTokenSource = cancellationTokenSource;
1031 // Cached callback delegate that's lazily initialized due to ContextCallback being SecurityCritical
1033 private static ContextCallback s_executionContextCallback;
1036 /// InternalExecuteCallbackSynchronously_GeneralPath
1037 /// This will be called on the target synchronization context, however, we still need to restore the required execution context
1039 [SecuritySafeCritical]
1040 internal void ExecuteCallback()
1042 if (TargetExecutionContext != null)
1044 // Lazily initialize the callback delegate; benign ----
1045 var callback = s_executionContextCallback;
1046 if (callback == null) s_executionContextCallback = callback = new ContextCallback(ExecutionContextCallback);
1048 ExecutionContext.Run(
1049 TargetExecutionContext,
1055 //otherwise run directly
1056 ExecutionContextCallback(this);
1060 // the worker method to actually run the callback
1061 // The signature is such that it can be used as a 'ContextCallback'
1063 private static void ExecutionContextCallback(object obj)
1065 CancellationCallbackInfo callbackInfo = obj as CancellationCallbackInfo;
1066 Contract.Assert(callbackInfo != null);
1067 callbackInfo.Callback(callbackInfo.StateForCallback);
1072 // ----------------------------------------------------------
1073 // -- SparselyPopulatedArray --
1074 // ----------------------------------------------------------
1077 /// A sparsely populated array. Elements can be sparse and some null, but this allows for
1078 /// lock-free additions and growth, and also for constant time removal (by nulling out).
1080 /// <typeparam name="T">The kind of elements contained within.</typeparam>
1081 internal class SparselyPopulatedArray<T> where T : class
1083 private readonly SparselyPopulatedArrayFragment<T> m_head;
1084 private volatile SparselyPopulatedArrayFragment<T> m_tail;
1087 /// Allocates a new array with the given initial size.
1089 /// <param name="initialSize">How many array slots to pre-allocate.</param>
1090 internal SparselyPopulatedArray(int initialSize)
1092 m_head = m_tail = new SparselyPopulatedArrayFragment<T>(initialSize);
1096 // Used in DEBUG mode by CancellationTokenSource.CallbackCount
1098 /// The head of the doubly linked list.
1100 internal SparselyPopulatedArrayFragment<T> Head
1102 get { return m_head; }
1107 /// The tail of the doubly linked list.
1109 internal SparselyPopulatedArrayFragment<T> Tail
1111 get { return m_tail; }
1115 /// Adds an element in the first available slot, beginning the search from the tail-to-head.
1116 /// If no slots are available, the array is grown. The method doesn't return until successful.
1118 /// <param name="element">The element to add.</param>
1119 /// <returns>Information about where the add happened, to enable O(1) deregistration.</returns>
1120 internal SparselyPopulatedArrayAddInfo<T> Add(T element)
1124 // Get the tail, and ensure it's up to date.
1125 SparselyPopulatedArrayFragment<T> tail = m_tail;
1126 while (tail.m_next != null)
1127 m_tail = (tail = tail.m_next);
1129 // Search for a free index, starting from the tail.
1130 SparselyPopulatedArrayFragment<T> curr = tail;
1131 while (curr != null)
1133 const int RE_SEARCH_THRESHOLD = -10; // Every 10 skips, force a search.
1134 if (curr.m_freeCount < 1)
1137 if (curr.m_freeCount > 0 || curr.m_freeCount < RE_SEARCH_THRESHOLD)
1139 int c = curr.Length;
1141 // We'll compute a start offset based on how many free slots we think there
1142 // are. This optimizes for ordinary the LIFO deregistration pattern, and is
1143 // far from perfect due to the non-threadsafe ++ and -- of the free counter.
1144 int start = ((c - curr.m_freeCount) % c);
1148 curr.m_freeCount--; // Too many free elements; fix up.
1150 Contract.Assert(start >= 0 && start < c, "start is outside of bounds");
1152 // Now walk the array until we find a free slot (or reach the end).
1153 for (int i = 0; i < c; i++)
1155 // If the slot is null, try to CAS our element into it.
1156 int tryIndex = (start + i) % c;
1157 Contract.Assert(tryIndex >= 0 && tryIndex < curr.m_elements.Length, "tryIndex is outside of bounds");
1159 if (curr.m_elements[tryIndex] == null && Interlocked.CompareExchange(ref curr.m_elements[tryIndex], element, null) == null)
1161 // We adjust the free count by --. Note: if this drops to 0, we will skip
1162 // the fragment on the next search iteration. Searching threads will -- the
1163 // count and force a search every so often, just in case fragmentation occurs.
1164 int newFreeCount = curr.m_freeCount - 1;
1165 curr.m_freeCount = newFreeCount > 0 ? newFreeCount : 0;
1166 return new SparselyPopulatedArrayAddInfo<T>(curr, tryIndex);
1174 // If we got here, we need to add a new chunk to the tail and try again.
1175 SparselyPopulatedArrayFragment<T> newTail = new SparselyPopulatedArrayFragment<T>(
1176 tail.m_elements.Length == 4096 ? 4096 : tail.m_elements.Length * 2, tail);
1177 if (Interlocked.CompareExchange(ref tail.m_next, newTail, null) == null)
1186 /// A struct to hold a link to the exact spot in an array an element was inserted, enabling
1187 /// constant time removal later on.
1189 internal struct SparselyPopulatedArrayAddInfo<T> where T : class
1191 private SparselyPopulatedArrayFragment<T> m_source;
1192 private int m_index;
1194 internal SparselyPopulatedArrayAddInfo(SparselyPopulatedArrayFragment<T> source, int index)
1196 Contract.Assert(source != null);
1197 Contract.Assert(index >= 0 && index < source.Length);
1202 internal SparselyPopulatedArrayFragment<T> Source
1204 get { return m_source; }
1209 get { return m_index; }
1214 /// A fragment of a sparsely populated array, doubly linked.
1216 /// <typeparam name="T">The kind of elements contained within.</typeparam>
1217 internal class SparselyPopulatedArrayFragment<T> where T : class
1219 internal readonly T[] m_elements; // The contents, sparsely populated (with nulls).
1220 internal volatile int m_freeCount; // A hint of the number of free elements.
1221 internal volatile SparselyPopulatedArrayFragment<T> m_next; // The next fragment in the chain.
1222 internal volatile SparselyPopulatedArrayFragment<T> m_prev; // The previous fragment in the chain.
1224 internal SparselyPopulatedArrayFragment(int size) : this(size, null)
1228 internal SparselyPopulatedArrayFragment(int size, SparselyPopulatedArrayFragment<T> prev)
1230 m_elements = new T[size];
1235 internal T this[int index]
1237 get { return Volatile.Read<T>(ref m_elements[index]); }
1242 get { return m_elements.Length; }
1246 // Used in DEBUG mode by CancellationTokenSource.CallbackCount
1247 internal SparselyPopulatedArrayFragment<T> Next
1249 get { return m_next; }
1252 internal SparselyPopulatedArrayFragment<T> Prev
1254 get { return m_prev; }
1257 // only removes the item at the specified index if it is still the expected one.
1258 // Returns the prevailing value.
1259 // The remove occured successfully if the return value == expected element
1260 // otherwise the remove did not occur.
1261 internal T SafeAtomicRemove(int index, T expectedElement)
1263 T prevailingValue = Interlocked.CompareExchange(ref m_elements[index], null, expectedElement);
1264 if (prevailingValue != null)
1266 return prevailingValue;