Expose an equivalent to Register that doesn't flow ExecutionContext and thus doesn't capture AsyncLocals.
// in the read/write copy loop.
if (cancellationToken.CanBeCanceled)
{
- cancellationReg = cancellationToken.Register(s =>
+ cancellationReg = cancellationToken.UnsafeRegister(s =>
{
var innerAwaitable = (AsyncCopyToAwaitable)s;
unsafe
long packedResult = Interlocked.CompareExchange(ref _result, RegisteringCancellation, NoResult);
if (packedResult == NoResult)
{
- _cancellationRegistration = cancellationToken.Register(cancelCallback, this);
+ _cancellationRegistration = cancellationToken.UnsafeRegister(cancelCallback, this);
// Switch the result, just in case IO completed while we were setting the registration
packedResult = Interlocked.Exchange(ref _result, NoResult);
public CancellationTokenRegistration Register(Action<object> callback, object state, bool useSynchronizationContext) =>
Register(callback, state, useSynchronizationContext, useExecutionContext: true);
- // helper for internal registration needs that don't require an EC capture (e.g. creating linked token sources, or registering unstarted TPL tasks)
- // has a handy signature, and skips capturing execution context.
- internal CancellationTokenRegistration InternalRegisterWithoutEC(Action<object> callback, object state) =>
+ /// <summary>
+ /// Registers a delegate that will be called when this
+ /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// If this token is already in the canceled state, the delegate will be run immediately and synchronously.
+ /// Any exception the delegate generates will be propagated out of this method call.
+ /// </para>
+ /// <para>
+ /// <see cref="System.Threading.ExecutionContext">ExecutionContext</see> is not captured nor flowed
+ /// to the callback's invocation.
+ /// </para>
+ /// </remarks>
+ /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param>
+ /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked. This may be null.</param>
+ /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can
+ /// be used to unregister the callback.</returns>
+ /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception>
+ public CancellationTokenRegistration UnsafeRegister(Action<object> callback, object state) =>
Register(callback, state, useSynchronizationContext: false, useExecutionContext: false);
/// <summary>
EnsureLockObjectCreated();
// We must register and unregister the token outside of the lock, to avoid deadlocks.
- using (cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCallback, this))
+ using (cancellationToken.UnsafeRegister(s_cancellationTokenCallback, this))
{
lock (m_lock)
{
//Register for cancellation outside of the main lock.
//NOTE: Register/unregister inside the lock can deadlock as different lock acquisition orders could
// occur for (1)this.m_lockObj and (2)cts.internalLock
- CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCanceledEventHandler, this);
+ CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.UnsafeRegister(s_cancellationTokenCanceledEventHandler, this);
try
{
// Perf: first spin wait for the count to be positive.
internal Linked1CancellationTokenSource(CancellationToken token1)
{
- _reg1 = token1.InternalRegisterWithoutEC(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
+ _reg1 = token1.UnsafeRegister(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
}
protected override void Dispose(bool disposing)
internal Linked2CancellationTokenSource(CancellationToken token1, CancellationToken token2)
{
- _reg1 = token1.InternalRegisterWithoutEC(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
- _reg2 = token2.InternalRegisterWithoutEC(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
+ _reg1 = token1.UnsafeRegister(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
+ _reg2 = token2.UnsafeRegister(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
}
protected override void Dispose(bool disposing)
{
if (tokens[i].CanBeCanceled)
{
- _linkingRegistrations[i] = tokens[i].InternalRegisterWithoutEC(s_linkedTokenCancelDelegate, this);
+ _linkingRegistrations[i] = tokens[i].UnsafeRegister(s_linkedTokenCancelDelegate, this);
}
// Empty slots in the array will be default(CancellationTokenRegistration), which are nops to Dispose.
// Based on usage patterns, such occurrences should also be rare, such that it's not worth resizing
if (antecedent == null)
{
// if no antecedent was specified, use this task's reference as the cancellation state object
- ctr = cancellationToken.InternalRegisterWithoutEC(s_taskCancelCallback, this);
+ ctr = cancellationToken.UnsafeRegister(s_taskCancelCallback, this);
}
else
{
// If an antecedent was specified, pack this task, its antecedent and the TaskContinuation together as a tuple
// and use it as the cancellation state object. This will be unpacked in the cancellation callback so that
// antecedent.RemoveCancellation(continuation) can be invoked.
- ctr = cancellationToken.InternalRegisterWithoutEC(s_taskCancelCallback,
+ ctr = cancellationToken.UnsafeRegister(s_taskCancelCallback,
new Tuple<Task, Task, TaskContinuation>(this, antecedent, continuation));
}
// Register our cancellation token, if necessary.
if (cancellationToken.CanBeCanceled)
{
- promise.Registration = cancellationToken.InternalRegisterWithoutEC(state => ((DelayPromise)state).Complete(), promise);
+ promise.Registration = cancellationToken.UnsafeRegister(state => ((DelayPromise)state).Complete(), promise);
}
// ... and create our timer and make sure that it stays rooted.