summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs')
-rw-r--r--src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs161
1 files changed, 161 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs b/src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs
new file mode 100644
index 0000000000..34e0bb0aba
--- /dev/null
+++ b/src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs
@@ -0,0 +1,161 @@
+// 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.Security.Permissions;
+using System.Runtime.CompilerServices;
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Represents a callback delegate that has been registered with a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
+ /// </summary>
+ /// <remarks>
+ /// To unregister a callback, dispose the corresponding Registration instance.
+ /// </remarks>
+ [HostProtection(Synchronization = true, ExternalThreading = true)]
+ public struct CancellationTokenRegistration : IEquatable<CancellationTokenRegistration>, IDisposable
+ {
+ private readonly CancellationCallbackInfo m_callbackInfo;
+ private readonly SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> m_registrationInfo;
+
+ internal CancellationTokenRegistration(
+ CancellationCallbackInfo callbackInfo,
+ SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> registrationInfo)
+ {
+ m_callbackInfo = callbackInfo;
+ m_registrationInfo = registrationInfo;
+ }
+
+ /// <summary>
+ /// Attempts to deregister the item. If it's already being run, this may fail.
+ /// Entails a full memory fence.
+ /// </summary>
+ /// <returns>True if the callback was found and deregistered, false otherwise.</returns>
+ [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;
+ }
+
+ /// <summary>
+ /// Disposes of the registration and unregisters the target callback from the associated
+ /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
+ /// 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.
+ /// </summary>
+ 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);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Determines whether two <see
+ /// cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see>
+ /// instances are equal.
+ /// </summary>
+ /// <param name="left">The first instance.</param>
+ /// <param name="right">The second instance.</param>
+ /// <returns>True if the instances are equal; otherwise, false.</returns>
+ public static bool operator ==(CancellationTokenRegistration left, CancellationTokenRegistration right)
+ {
+ return left.Equals(right);
+ }
+
+ /// <summary>
+ /// Determines whether two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are not equal.
+ /// </summary>
+ /// <param name="left">The first instance.</param>
+ /// <param name="right">The second instance.</param>
+ /// <returns>True if the instances are not equal; otherwise, false.</returns>
+ public static bool operator !=(CancellationTokenRegistration left, CancellationTokenRegistration right)
+ {
+ return !left.Equals(right);
+ }
+
+ /// <summary>
+ /// Determines whether the current <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instance is equal to the
+ /// specified <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The other object to which to compare this instance.</param>
+ /// <returns>True, if both this and <paramref name="obj"/> are equal. False, otherwise.
+ /// Two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are equal if
+ /// they both refer to the output of a single call to the same Register method of a
+ /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
+ /// </returns>
+ public override bool Equals(object obj)
+ {
+ return ((obj is CancellationTokenRegistration) && Equals((CancellationTokenRegistration) obj));
+ }
+
+ /// <summary>
+ /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the
+ /// specified <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="other">The other <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> to which to compare this instance.</param>
+ /// <returns>True, if both this and <paramref name="other"/> are equal. False, otherwise.
+ /// Two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are equal if
+ /// they both refer to the output of a single call to the same Register method of a
+ /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
+ /// </returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration.</see>.
+ /// </summary>
+ /// <returns>A hash code for the current <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instance.</returns>
+ public override int GetHashCode()
+ {
+ if (m_registrationInfo.Source != null)
+ return m_registrationInfo.Source.GetHashCode() ^ m_registrationInfo.Index.GetHashCode();
+
+ return m_registrationInfo.Index.GetHashCode();
+ }
+ }
+}