// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
//
// [....]
////////////////////////////////////////////////////////////////////////////////
using System.Diagnostics.Contracts;
using System.Security.Permissions;
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.
///
[HostProtection(Synchronization = true, ExternalThreading = true)]
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 deregisterOccured = 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
!deregisterOccured && //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();
}
}
}