// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // // [....] //////////////////////////////////////////////////////////////////////////////// #pragma warning disable 0420 // turn off 'a reference to a volatile field will not be treated as volatile' during CAS. using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Diagnostics.Contracts; using System.Runtime; using System.Runtime.CompilerServices; using System.Security; namespace System.Threading { /// /// Propagates notification that operations should be canceled. /// /// /// /// A may be created directly in an unchangeable canceled or non-canceled state /// using the CancellationToken's constructors. However, to have a CancellationToken that can change /// from a non-canceled to a canceled state, /// CancellationTokenSource must be used. /// CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its /// Token property. /// /// /// Once canceled, a token may not transition to a non-canceled state, and a token whose /// is false will never change to one that can be canceled. /// /// /// All members of this struct are thread-safe and may be used concurrently from multiple threads. /// /// [ComVisible(false)] [HostProtection(Synchronization = true, ExternalThreading = true)] [DebuggerDisplay("IsCancellationRequested = {IsCancellationRequested}")] public struct CancellationToken { // The backing TokenSource. // if null, it implicitly represents the same thing as new CancellationToken(false). // When required, it will be instantiated to reflect this. private CancellationTokenSource m_source; //!! warning. If more fields are added, the assumptions in CreateLinkedToken may no longer be valid /* Properties */ /// /// Returns an empty CancellationToken value. /// /// /// The value returned by this property will be non-cancelable by default. /// public static CancellationToken None { #if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif get { return default(CancellationToken); } } /// /// Gets whether cancellation has been requested for this token. /// /// Whether cancellation has been requested for this token. /// /// /// This property indicates whether cancellation has been requested for this token, /// either through the token initially being construted in a canceled state, or through /// calling Cancel /// on the token's associated . /// /// /// If this property is true, it only guarantees that cancellation has been requested. /// It does not guarantee that every registered handler /// has finished executing, nor that cancellation requests have finished propagating /// to all registered handlers. Additional synchronization may be required, /// particularly in situations where related objects are being canceled concurrently. /// /// public bool IsCancellationRequested { #if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif get { return m_source != null && m_source.IsCancellationRequested; } } /// /// Gets whether this token is capable of being in the canceled state. /// /// /// If CanBeCanceled returns false, it is guaranteed that the token will never transition /// into a canceled state, meaning that will never /// return true. /// public bool CanBeCanceled { #if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif get { return m_source != null && m_source.CanBeCanceled; } } /// /// Gets a that is signaled when the token is canceled. /// /// Accessing this property causes a WaitHandle /// to be instantiated. It is preferable to only use this property when necessary, and to then /// dispose the associated instance at the earliest opportunity (disposing /// the source will dispose of this allocated handle). The handle should not be closed or disposed directly. /// /// The associated CancellationTokenSource has been disposed. public WaitHandle WaitHandle { get { if (m_source == null) { InitializeDefaultSource(); } return m_source.WaitHandle; } } // public CancellationToken() // this constructor is implicit for structs // -> this should behaves exactly as for new CancellationToken(false) /// /// Internal constructor only a CancellationTokenSource should create a CancellationToken /// #if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif internal CancellationToken(CancellationTokenSource source) { m_source = source; } /// /// Initializes the CancellationToken. /// /// /// The canceled state for the token. /// /// /// Tokens created with this constructor will remain in the canceled state specified /// by the parameter. If is false, /// both and will be false. /// If is true, /// both and will be true. /// public CancellationToken(bool canceled) : this() { if(canceled) m_source = CancellationTokenSource.InternalGetStaticSource(canceled); } /* Methods */ private readonly static Action s_ActionToActionObjShunt = new Action(ActionToActionObjShunt); private static void ActionToActionObjShunt(object obj) { Action action = obj as Action; Contract.Assert(action != null, "Expected an Action here"); action(); } /// /// Registers a delegate that will be called when this CancellationToken is canceled. /// /// /// /// If this token is already in the canceled state, the /// delegate will be run immediately and synchronously. Any exception the delegate generates will be /// propagated out of this method call. /// /// /// The current ExecutionContext, if one exists, will be captured /// along with the delegate and will be used when executing it. /// /// /// The delegate to be executed when the CancellationToken is canceled. /// The instance that can /// be used to deregister the callback. /// is null. /// The associated CancellationTokenSource has been disposed. public CancellationTokenRegistration Register(Action callback) { if (callback == null) throw new ArgumentNullException("callback"); return Register( s_ActionToActionObjShunt, callback, false, // useSync=false true // useExecutionContext=true ); } /// /// Registers a delegate that will be called when this /// CancellationToken is canceled. /// /// /// /// If this token is already in the canceled state, the /// delegate will be run immediately and synchronously. Any exception the delegate generates will be /// propagated out of this method call. /// /// /// The current ExecutionContext, if one exists, will be captured /// along with the delegate and will be used when executing it. /// /// /// The delegate to be executed when the CancellationToken is canceled. /// A Boolean value that indicates whether to capture /// the current SynchronizationContext and use it /// when invoking the . /// The instance that can /// be used to deregister the callback. /// is null. /// The associated CancellationTokenSource has been disposed. public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext) { if (callback == null) throw new ArgumentNullException("callback"); return Register( s_ActionToActionObjShunt, callback, useSynchronizationContext, true // useExecutionContext=true ); } /// /// Registers a delegate that will be called when this /// CancellationToken is canceled. /// /// /// /// If this token is already in the canceled state, the /// delegate will be run immediately and synchronously. Any exception the delegate generates will be /// propagated out of this method call. /// /// /// The current ExecutionContext, if one exists, will be captured /// along with the delegate and will be used when executing it. /// /// /// The delegate to be executed when the CancellationToken is canceled. /// The state to pass to the when the delegate is invoked. This may be null. /// The instance that can /// be used to deregister the callback. /// is null. /// The associated CancellationTokenSource has been disposed. public CancellationTokenRegistration Register(Action callback, Object state) { if (callback == null) throw new ArgumentNullException("callback"); return Register( callback, state, false, // useSync=false true // useExecutionContext=true ); } /// /// Registers a delegate that will be called when this /// CancellationToken is canceled. /// /// /// /// If this token is already in the canceled state, the /// delegate will be run immediately and synchronously. Any exception the delegate generates will be /// propagated out of this method call. /// /// /// The current ExecutionContext, if one exists, /// will be captured along with the delegate and will be used when executing it. /// /// /// The delegate to be executed when the CancellationToken is canceled. /// The state to pass to the when the delegate is invoked. This may be null. /// A Boolean value that indicates whether to capture /// the current SynchronizationContext and use it /// when invoking the . /// The instance that can /// be used to deregister the callback. /// is null. /// The associated CancellationTokenSource has been disposed. public CancellationTokenRegistration Register(Action callback, Object state, bool useSynchronizationContext) { return Register( callback, state, useSynchronizationContext, true // useExecutionContext=true ); } // helper for internal registration needs that don't require an EC capture (e.g. creating linked token sources, or registering unstarted TPL tasks) // has a handy signature, and skips capturing execution context. internal CancellationTokenRegistration InternalRegisterWithoutEC(Action callback, Object state) { return Register( callback, state, false, // useSyncContext=false false // useExecutionContext=false ); } // the real work.. [SecuritySafeCritical] [MethodImpl(MethodImplOptions.NoInlining)] private CancellationTokenRegistration Register(Action callback, Object state, bool useSynchronizationContext, bool useExecutionContext) { StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; if (callback == null) throw new ArgumentNullException("callback"); if (CanBeCanceled == false) { return new CancellationTokenRegistration(); // nothing to do for tokens than can never reach the canceled state. Give them a dummy registration. } // Capture [....]/execution contexts if required. // Note: Only capture [....]/execution contexts if IsCancellationRequested = false // as we know that if it is true that the callback will just be called synchronously. SynchronizationContext capturedSyncContext = null; ExecutionContext capturedExecutionContext = null; if (!IsCancellationRequested) { if (useSynchronizationContext) capturedSyncContext = SynchronizationContext.Current; if (useExecutionContext) capturedExecutionContext = ExecutionContext.Capture( ref stackMark, ExecutionContext.CaptureOptions.OptimizeDefaultCase); // ideally we'd also use IgnoreSyncCtx, but that could break compat } // Register the callback with the source. return m_source.InternalRegister(callback, state, capturedSyncContext, capturedExecutionContext); } /// /// Determines whether the current CancellationToken instance is equal to the /// specified token. /// /// The other CancellationToken to which to compare this /// instance. /// True if the instances are equal; otherwise, false. Two tokens are equal if they are associated /// with the same CancellationTokenSource or if they were both constructed /// from public CancellationToken constructors and their values are equal. public bool Equals(CancellationToken other) { //if both sources are null, then both tokens represent the Empty token. if (m_source == null && other.m_source == null) { return true; } // one is null but other has inflated the default source // these are only equal if the inflated one is the staticSource(false) if (m_source == null) { return other.m_source == CancellationTokenSource.InternalGetStaticSource(false); } if (other.m_source == null) { return m_source == CancellationTokenSource.InternalGetStaticSource(false); } // general case, we check if the sources are identical return m_source == other.m_source; } /// /// Determines whether the current CancellationToken instance is equal to the /// specified . /// /// The other object to which to compare this instance. /// True if is a CancellationToken /// and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated /// with the same CancellationTokenSource or if they were both constructed /// from public CancellationToken constructors and their values are equal. /// An associated CancellationTokenSource has been disposed. public override bool Equals(Object other) { if (other is CancellationToken) { return Equals((CancellationToken) other); } return false; } /// /// Serves as a hash function for a CancellationToken. /// /// A hash code for the current CancellationToken instance. public override Int32 GetHashCode() { if (m_source == null) { // link to the common source so that we have a source to interrogate. return CancellationTokenSource.InternalGetStaticSource(false).GetHashCode(); } return m_source.GetHashCode(); } /// /// Determines whether two CancellationToken instances are equal. /// /// The first instance. /// The second instance. /// True if the instances are equal; otherwise, false. /// An associated CancellationTokenSource has been disposed. public static bool operator ==(CancellationToken left, CancellationToken right) { return left.Equals(right); } /// /// Determines whether two CancellationToken instances are not equal. /// /// The first instance. /// The second instance. /// True if the instances are not equal; otherwise, false. /// An associated CancellationTokenSource has been disposed. public static bool operator !=(CancellationToken left, CancellationToken right) { return !left.Equals(right); } /// /// Throws a OperationCanceledException if /// this token has had cancellation requested. /// /// /// This method provides functionality equivalent to: /// /// if (token.IsCancellationRequested) /// throw new OperationCanceledException(token); /// /// /// The token has had cancellation requested. /// The associated CancellationTokenSource has been disposed. #if !FEATURE_CORECLR [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] #endif public void ThrowIfCancellationRequested() { if (IsCancellationRequested) ThrowOperationCanceledException(); } // Throw an ODE if this CancellationToken's source is disposed. internal void ThrowIfSourceDisposed() { if ((m_source != null) && m_source.IsDisposed) ThrowObjectDisposedException(); } // Throws an OCE; separated out to enable better inlining of ThrowIfCancellationRequested private void ThrowOperationCanceledException() { throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this); } private static void ThrowObjectDisposedException() { throw new ObjectDisposedException(null, Environment.GetResourceString("CancellationToken_SourceDisposed")); } // ----------------------------------- // Private helpers private void InitializeDefaultSource() { // Lazy is slower, and although multiple threads may ---- and set m_source repeatedly, the ---- is benign. // Alternative: LazyInititalizer.EnsureInitialized(ref m_source, ()=>CancellationTokenSource.InternalGetStaticSource(false)); m_source = CancellationTokenSource.InternalGetStaticSource(false); } } }