// 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.Threading.Tasks; namespace System.Threading { /// /// Represents a callback delegate that has been registered with a CancellationToken. /// /// /// To unregister a callback, dispose the corresponding Registration instance. /// public readonly struct CancellationTokenRegistration : IEquatable, IDisposable, IAsyncDisposable { private readonly long _id; private readonly CancellationTokenSource.CallbackNode _node; internal CancellationTokenRegistration(long id, CancellationTokenSource.CallbackNode node) { _id = id; _node = node; } /// /// 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 unregisters itself. /// public void Dispose() { CancellationTokenSource.CallbackNode node = _node; if (node != null && !node.Partition.Unregister(_id, node)) { WaitForCallbackIfNecessary(); } } /// /// Disposes of the registration and unregisters the target callback from the associated /// CancellationToken. /// The returned will complete once the associated callback /// is unregistered without having executed or once it's finished executing, except /// in the degenerate case where the callback itself is unregistering itself. /// public ValueTask DisposeAsync() { CancellationTokenSource.CallbackNode node = _node; return node != null && !node.Partition.Unregister(_id, node) ? WaitForCallbackIfNecessaryAsync() : default; } /// /// Gets the with which this registration is associated. If the /// registration isn't associated with a token (such as after the registration has been disposed), /// this will return a default token. /// public CancellationToken Token { get { CancellationTokenSource.CallbackNode node = _node; return node != null ? new CancellationToken(node.Partition.Source) : // avoid CTS.Token, which throws after disposal default; } } /// /// Disposes of the registration and unregisters the target callback from the associated /// CancellationToken. /// public bool Unregister() { CancellationTokenSource.CallbackNode node = _node; return node != null && node.Partition.Unregister(_id, node); } private void WaitForCallbackIfNecessary() { // We're a valid registration but we were unable to unregister, which means the callback wasn't in the list, // which means either it already executed or it's currently executing. 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 unregistration 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 unregister. CancellationTokenSource source = _node.Partition.Source; if (source.IsCancellationRequested && // Running callbacks has commenced. !source.IsCancellationCompleted && // Running callbacks hasn't finished. source.ThreadIDExecutingCallbacks != Environment.CurrentManagedThreadId) // The executing thread ID is not this thread's ID. { // Callback execution is in progress, the executing thread is different from this thread and has taken the callback for execution // so observe and wait until this target callback is no longer the executing callback. source.WaitForCallbackToComplete(_id); } } private ValueTask WaitForCallbackIfNecessaryAsync() { // Same as WaitForCallbackIfNecessary, except returning a task that'll be completed when callbacks complete. CancellationTokenSource source = _node.Partition.Source; if (source.IsCancellationRequested && // Running callbacks has commenced. !source.IsCancellationCompleted && // Running callbacks hasn't finished. source.ThreadIDExecutingCallbacks != Environment.CurrentManagedThreadId) // The executing thread ID is not this thread's ID. { // Callback execution is in progress, the executing thread is different from this thread and has taken the callback for execution // so get a task that'll complete when this target callback is no longer the executing callback. return source.WaitForCallbackToCompleteAsync(_id); } // Callback is either already completed, won't execute, or the callback itself is calling this. return default; } /// /// 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) => 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) => !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) => 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) => _node == other._node && _id == other._id; /// /// Serves as a hash function for a CancellationTokenRegistration.. /// /// A hash code for the current CancellationTokenRegistration instance. public override int GetHashCode() => _node != null ? _node.GetHashCode() ^ _id.GetHashCode() : _id.GetHashCode(); } }