How do I know if a CancellationToken has a registered cancellation method?
I have a parent containing a CancellationTokenSource
. This object passes its own CancellationToken
to the process, which communicates sequentially with external services. Whenever a call is made to an external service, it is CancellationToken
registered with a method that will allow the process to stop waiting for a response from the external service:
myObj.CancellationTokenRegistration.Dispose();
myObj.CancellationTokenRegistration = myObj.CancellationToken.Register(() => CancelMethod(myObj));
Given only CancellationTokenSource
, is there a way to know that the cancellation method was registered against the token?
source to share
Without using reflection (or other types of voodoo) to look at the internal state CancellationTokenSource
, there is no way to tell if any code has added any registrations.
If you want to use reflection, you should look at this field:
private volatile SparselyPopulatedArray<CancellationCallbackInfo>[] m_registeredCallbacksLists;
source to share
The m_callbackInfo field appears to contain the same information.
CancellationTokenSource cts = new CancellationTokenSource();
Action test = CancelMethod;
CancellationTokenRegistration = cts.Token.Register(test);
var fieldInfo = typeof(CancellationTokenRegistration).GetField("m_callbackInfo", BindingFlags.NonPublic | BindingFlags.Instance);
object fieldValue = fieldInfo.GetValue(CancellationTokenRegistration);
var callbackFieldInfo = fieldValue.GetType().GetField("Callback", BindingFlags.Instance | BindingFlags.NonPublic);
var callbackValue = callbackFieldInfo.GetValue(fieldValue);
var stateForCallbackFieldInfo = fieldValue.GetType().GetField("StateForCallback", BindingFlags.Instance | BindingFlags.NonPublic);
var stateForCallbackValue = stateForCallbackFieldInfo.GetValue(fieldValue);
// stateForCallbackValue == CancelMethod; if Token.Register is called with one of the Action<object> arguments
// callbackValue == CancelMethod
private void CancelMethod()
{
throw new System.NotImplementedException();
}
source to share
Here is some example code using reflection to enumerate registered actions (works as expected in .Net 4.7.1):
public static IEnumerable<Action<object>> Registrations(this CancellationToken token)
{
var sourceFieldInfo = typeof(CancellationToken).GetField("m_source", BindingFlags.NonPublic | BindingFlags.Instance);
var cancellationTokenSource = (CancellationTokenSource)sourceFieldInfo.GetValue(token);
var callbacksFieldInfo = typeof(CancellationTokenSource).GetField("m_registeredCallbacksLists", BindingFlags.NonPublic | BindingFlags.Instance);
var callbaskLists = (Array)callbacksFieldInfo.GetValue(cancellationTokenSource);
foreach (var sparselyPopulatedArray in callbaskLists)
{
if (sparselyPopulatedArray == null)
{
continue;
}
var sparselyPopulatedArrayType = sparselyPopulatedArray.GetType();
var tailFieldInfo = sparselyPopulatedArrayType.GetProperty("Tail", BindingFlags.NonPublic | BindingFlags.Instance);
var tail = tailFieldInfo.GetValue(sparselyPopulatedArray);
var sparselyPopulatedArrayFragmentType = tail.GetType();
var elementsTypeFieldInfo = sparselyPopulatedArrayFragmentType.GetField("m_elements", BindingFlags.NonPublic | BindingFlags.Instance);
var elements = (Array)elementsTypeFieldInfo.GetValue(tail);
foreach (var callbackInfo in elements)
{
if (callbackInfo == null)
{
continue;
}
var callbackInfoType = callbackInfo.GetType();
var callbackFieldInfo = callbackInfoType.GetField("Callback", BindingFlags.NonPublic | BindingFlags.Instance);
var callback = (Action<object>)callbackFieldInfo.GetValue(callbackInfo);
if (callback != null)
{
yield return callback;
}
}
}
}
source to share