Add CancellationToken.UnsafeRegister (#20342)
authorStephen Toub <stoub@microsoft.com>
Wed, 10 Oct 2018 21:10:12 +0000 (17:10 -0400)
committerGitHub <noreply@github.com>
Wed, 10 Oct 2018 21:10:12 +0000 (17:10 -0400)
Expose an equivalent to Register that doesn't flow ExecutionContext and thus doesn't capture AsyncLocals.

src/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs
src/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs
src/System.Private.CoreLib/shared/System/Threading/CancellationToken.cs
src/System.Private.CoreLib/shared/System/Threading/ManualResetEventSlim.cs
src/System.Private.CoreLib/shared/System/Threading/SemaphoreSlim.cs
src/System.Private.CoreLib/src/System/Threading/CancellationTokenSource.cs
src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs

index ddbac1b..4f8292b 100644 (file)
@@ -1318,7 +1318,7 @@ namespace System.IO
                 // in the read/write copy loop.
                 if (cancellationToken.CanBeCanceled)
                 {
-                    cancellationReg = cancellationToken.Register(s =>
+                    cancellationReg = cancellationToken.UnsafeRegister(s =>
                     {
                         var innerAwaitable = (AsyncCopyToAwaitable)s;
                         unsafe
index 62ace09..ec499d8 100644 (file)
@@ -87,7 +87,7 @@ namespace System.IO
                     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);
index 68e6971..8088434 100644 (file)
@@ -220,9 +220,26 @@ namespace System.Threading
         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>
index dc65f6f..dab6bd9 100644 (file)
@@ -566,7 +566,7 @@ namespace System.Threading
                 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)
                     {
index 83f5f1d..a8c4984 100644 (file)
@@ -333,7 +333,7 @@ namespace System.Threading
             //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.
index dba11ce..291e536 100644 (file)
@@ -774,7 +774,7 @@ namespace System.Threading
 
             internal Linked1CancellationTokenSource(CancellationToken token1)
             {
-                _reg1 = token1.InternalRegisterWithoutEC(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
+                _reg1 = token1.UnsafeRegister(LinkedNCancellationTokenSource.s_linkedTokenCancelDelegate, this);
             }
 
             protected override void Dispose(bool disposing)
@@ -796,8 +796,8 @@ namespace System.Threading
 
             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)
@@ -827,7 +827,7 @@ namespace System.Threading
                 {
                     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
index 3bce5fb..d6e4103 100644 (file)
@@ -632,14 +632,14 @@ namespace System.Threading.Tasks
                         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));
                         }
 
@@ -5419,7 +5419,7 @@ namespace System.Threading.Tasks
             // 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.