I need to register a listener to the token while holding a non-reentrant lock (like SpinLock). If the callback is synchronously invoked due to the token having been canceled, it will cause a deadlock. It's impossible to simply check the IsCancellationRequested property before registering, because it could still be canceled on another thread, causing a race condition.
API Proposal
namespace System.Threading;
public struct CancellationToken
{
public CancellationTokenRegistration UnsafeRegisterWithoutImmediateInvoke(Action<object?> callback, object? state, out bool alreadyCanceled);
public CancellationTokenRegistration UnsafeRegisterWithoutImmediateInvoke(Action<object?, CancellationToken> callback, object? state, out bool alreadyCanceled);
}
alreadyCanceled is set to true if the callback would have been invoked using the existing register APIs.
API Usage
_spinLock.Enter();
var obj = new MyAsyncObject(this);
obj._registration = cancellationToken.UnsafeRegisterWithoutImmediateInvoke(o => ((MyAsyncObject) o).Cancel(), obj, out bool alreadyCanceled);
if (!alreadyCanceled)
{
_queue.Enqueue(obj);
}
_spinLock.Exit();
Alternative Designs
Alternative 1 - use a more expensive reentrant lock.
Alternative 2 - use a [ThreadStatic] field to determine if the callback is invoked synchronously.
Background and motivation
I need to register a listener to the token while holding a non-reentrant lock (like SpinLock). If the callback is synchronously invoked due to the token having been canceled, it will cause a deadlock. It's impossible to simply check the
IsCancellationRequested
property before registering, because it could still be canceled on another thread, causing a race condition.API Proposal
alreadyCanceled
is set to true if the callback would have been invoked using the existing register APIs.API Usage
Alternative Designs
Alternative 1 - use a more expensive reentrant lock.
Alternative 2 - use a
[ThreadStatic]
field to determine if the callback is invoked synchronously.Risks
No response