// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // //////////////////////////////////////////////////////////////////////////////// using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; namespace System.Threading { /// /// Represents a callback delegate that has been registered with a CancellationToken. /// /// /// To unregister a callback, dispose the corresponding Registration instance. /// public struct CancellationTokenRegistration : IEquatable, IDisposable { private readonly CancellationCallbackInfo m_callbackInfo; private readonly SparselyPopulatedArrayAddInfo m_registrationInfo; internal CancellationTokenRegistration( CancellationCallbackInfo callbackInfo, SparselyPopulatedArrayAddInfo registrationInfo) { m_callbackInfo = callbackInfo; m_registrationInfo = registrationInfo; } /// /// Attempts to deregister the item. If it's already being run, this may fail. /// Entails a full memory fence. /// /// True if the callback was found and deregistered, false otherwise. [FriendAccessAllowed] internal bool TryDeregister() { if (m_registrationInfo.Source == null) //can be null for dummy registrations. return false; // Try to remove the callback info from the array. // It is possible the callback info is missing (removed for run, or removed by someone else) // It is also possible there is info in the array but it doesn't match our current registration's callback info. CancellationCallbackInfo prevailingCallbackInfoInSlot = m_registrationInfo.Source.SafeAtomicRemove(m_registrationInfo.Index, m_callbackInfo); if (prevailingCallbackInfoInSlot != m_callbackInfo) return false; //the callback in the slot wasn't us. return true; } /// /// Disposes of the registration and unregisters the target callback from the associated /// CancellationToken. /// If the target callback is currently executing this method will wait until it completes, except /// in the degenerate cases where a callback method deregisters itself. /// public void Dispose() { // Remove the entry from the array. // This call includes a full memory fence which prevents potential reorderings of the reads below bool deregisterOccurred = TryDeregister(); // We guarantee that we will not return if the callback is being executed (assuming we are not currently called by the callback itself) // We achieve this by the following rules: // 1. if we are called in the context of an executing callback, no need to wait (determined by tracking callback-executor threadID) // - if the currently executing callback is this CTR, then waiting would deadlock. (We choose to return rather than deadlock) // - if not, then this CTR cannot be the one executing, hence no need to wait // // 2. if deregistration failed, and we are on a different thread, then the callback may be running under control of cts.Cancel() // => poll until cts.ExecutingCallback is not the one we are trying to deregister. var callbackInfo = m_callbackInfo; if (callbackInfo != null) { var tokenSource = callbackInfo.CancellationTokenSource; if (tokenSource.IsCancellationRequested && //running callbacks has commenced. !tokenSource.IsCancellationCompleted && //running callbacks hasn't finished !deregisterOccurred && //deregistration failed (ie the callback is missing from the list) tokenSource.ThreadIDExecutingCallbacks != Thread.CurrentThread.ManagedThreadId) //the executingThreadID is not this threadID. { // Callback execution is in progress, the executing thread is different to us and has taken the callback for execution // so observe and wait until this target callback is no longer the executing callback. tokenSource.WaitForCallbackToComplete(m_callbackInfo); } } } /// /// Determines whether two CancellationTokenRegistration /// instances are equal. /// /// The first instance. /// The second instance. /// True if the instances are equal; otherwise, false. public static bool operator ==(CancellationTokenRegistration left, CancellationTokenRegistration right) { return left.Equals(right); } /// /// Determines whether two CancellationTokenRegistration instances are not equal. /// /// The first instance. /// The second instance. /// True if the instances are not equal; otherwise, false. public static bool operator !=(CancellationTokenRegistration left, CancellationTokenRegistration right) { return !left.Equals(right); } /// /// Determines whether the current CancellationTokenRegistration instance is equal to the /// specified . /// /// The other object to which to compare this instance. /// True, if both this and are equal. False, otherwise. /// Two CancellationTokenRegistration instances are equal if /// they both refer to the output of a single call to the same Register method of a /// CancellationToken. /// public override bool Equals(object obj) { return ((obj is CancellationTokenRegistration) && Equals((CancellationTokenRegistration)obj)); } /// /// Determines whether the current CancellationToken instance is equal to the /// specified . /// /// The other CancellationTokenRegistration to which to compare this instance. /// True, if both this and are equal. False, otherwise. /// Two CancellationTokenRegistration instances are equal if /// they both refer to the output of a single call to the same Register method of a /// CancellationToken. /// public bool Equals(CancellationTokenRegistration other) { return m_callbackInfo == other.m_callbackInfo && m_registrationInfo.Source == other.m_registrationInfo.Source && m_registrationInfo.Index == other.m_registrationInfo.Index; } /// /// Serves as a hash function for a CancellationTokenRegistration.. /// /// A hash code for the current CancellationTokenRegistration instance. public override int GetHashCode() { if (m_registrationInfo.Source != null) return m_registrationInfo.Source.GetHashCode() ^ m_registrationInfo.Index.GetHashCode(); return m_registrationInfo.Index.GetHashCode(); } } }