164 lines
8.7 KiB
C#
164 lines
8.7 KiB
C#
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
//
|
|
// <OWNER>[....]</OWNER>
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
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 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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();
|
|
}
|
|
}
|
|
}
|