public static void CancellationTokenRegistration_UnregisterRemovesDelegate()
        {
            var  cts     = new CancellationTokenSource();
            bool invoked = false;
            CancellationTokenRegistration ctr = cts.Token.Register(() => invoked = true);

            Assert.True(ctr.Unregister());
            Assert.False(ctr.Unregister());
            cts.Cancel();
            Assert.False(invoked);
        }
        public static void CancellationTokenRegistration_UnregisterDuringCancellation_SuccessfullyRemovedIfNotYetInvoked()
        {
            var ctr0running = new ManualResetEventSlim();
            var ctr2blocked = new ManualResetEventSlim();
            var ctr2running = new ManualResetEventSlim();
            var cts         = new CancellationTokenSource();

            CancellationTokenRegistration ctr0 = cts.Token.Register(() => ctr0running.Set());

            bool ctr1Invoked = false;
            CancellationTokenRegistration ctr1 = cts.Token.Register(() => ctr1Invoked = true);

            CancellationTokenRegistration ctr2 = cts.Token.Register(() =>
            {
                ctr2running.Set();
                ctr2blocked.Wait();
            });

            // Cancel.  This will trigger ctr2 to run, then ctr1, then ctr0.
            Task.Run(() => cts.Cancel());
            ctr2running.Wait(); // wait for ctr2 to start running
            Assert.False(ctr2.Unregister());

            // Now that ctr2 is running, unregister ctr1. This should succeed
            // and ctr1 should not run.
            Assert.True(ctr1.Unregister());

            // Allow ctr2 to continue.  ctr1 should not run.  ctr0 should, so wait for it.
            ctr2blocked.Set();
            ctr0running.Wait();
            Assert.False(ctr0.Unregister());
            Assert.False(ctr1Invoked);
        }
예제 #3
0
            public CancellationToken UnregisterAndGetCancellationToken()
            {
                lock (this)
                {
                    _cancellationContext = null;
                    _cancellationRegistration.Unregister();
                }

                return(_cancellationRegistration.Token);
            }
        public static async Task CancellationTokenRegistration_ConcurrentUnregisterWithCancel_ReturnsFalseOrCallbackInvoked()
        {
            using (Barrier barrier = new Barrier(2))
            {
                const int Iters = 10_000;
                CancellationTokenSource cts = new CancellationTokenSource();
                bool unregisterResult = false, callbackInvoked = false;

                var tasks = new Task[]
                {
                    // Register and unregister
                    Task.Factory.StartNew(() =>
                    {
                        for (int i = 0; i < Iters; i++)
                        {
                            barrier.SignalAndWait();
                            CancellationTokenRegistration ctr = cts.Token.Register(() => callbackInvoked = true);
                            barrier.SignalAndWait();
                            unregisterResult = ctr.Unregister();
                            barrier.SignalAndWait();
                        }
                    }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default),

                    // Cancel, and validate the results
                    Task.Factory.StartNew(() =>
                    {
                        for (int i = 0; i < Iters; i++)
                        {
                            barrier.SignalAndWait();
                            barrier.SignalAndWait();
                            cts.Cancel();
                            barrier.SignalAndWait();

                            Assert.True(unregisterResult ^ callbackInvoked);

                            unregisterResult = callbackInvoked = false;
                            cts = new CancellationTokenSource();
                        }
                    }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)
                };

                // wait for one to fail or both to complete
                await await Task.WhenAny(tasks);

                await Task.WhenAll(tasks);
            }
        }
        public static void CancellationTokenRegistration_UnregisterWhileCallbackRunning_UnregisterDoesntWaitForCallbackToComplete()
        {
            using (var barrier = new Barrier(2))
            {
                var cts = new CancellationTokenSource();
                CancellationTokenRegistration ctr = cts.Token.Register(() =>
                {
                    barrier.SignalAndWait();
                    barrier.SignalAndWait();
                });

                Task.Run(() => cts.Cancel());

                // Validate that Unregister doesn't block waiting for the callback to complete.
                // (If it did block, this would deadlock.)
                barrier.SignalAndWait();
                Assert.False(ctr.Unregister());
                barrier.SignalAndWait();
            }
        }
        public static void CancellationTokenSource_Ctor_ZeroTimeout(bool timeSpan)
        {
            var cts = timeSpan ?
                      new CancellationTokenSource(TimeSpan.Zero) :
                      new CancellationTokenSource(0);

            Assert.True(cts.IsCancellationRequested);
            Assert.True(cts.Token.IsCancellationRequested);

            Assert.NotEqual(CancellationToken.None, cts.Token);
            Assert.NotEqual(new CancellationTokenSource(0).Token, cts.Token);

            for (int i = 0; i < 2; i++)
            {
                int invokedCount = 0;
                CancellationTokenRegistration r = cts.Token.Register(() => invokedCount++);
                Assert.Equal(1, invokedCount);
                Assert.False(r.Unregister());
            }
        }
        public static void Run(Action action, CancellationToken cancellationToken)
        {
            ArgumentNullException.ThrowIfNull(action);

            // ControlledExecution.Run does not support nested invocations.  If there's one already in flight
            // on this thread, fail.
            if (t_executing)
            {
                throw new InvalidOperationException(SR.InvalidOperation_NestedControlledExecutionRun);
            }

            // Store the current thread so that it may be referenced by the Canceler.Cancel callback if one occurs.
            Canceler canceler = new(Thread.CurrentThread);

            try
            {
                // Mark this thread as now running a ControlledExecution.Run to prevent recursive usage.
                t_executing = true;

                // Register for aborting.  From this moment until ctr.Unregister is called, this thread is subject to being
                // interrupted at any moment.  This could happen during the call to UnsafeRegister if cancellation has
                // already been requested at the time of the registration.
                CancellationTokenRegistration ctr = cancellationToken.UnsafeRegister(e => ((Canceler)e !).Cancel(), canceler);
                try
                {
                    // Invoke the caller's code.
                    action();
                }
                finally
                {
                    // This finally block may be cloned by JIT for the non-exceptional code flow.  In that case the code
                    // below is not guarded against aborting.  That is OK as the outer try block will catch the
                    // ThreadAbortException and call ResetAbortThread.

                    // Unregister the callback.  Unlike Dispose, Unregister will not block waiting for an callback in flight
                    // to complete, and will instead return false if the callback has already been invoked or is currently
                    // in flight.
                    if (!ctr.Unregister())
                    {
                        // Wait until the callback has completed.  Either the callback is already invoked and completed
                        // (in which case IsCancelCompleted will be true), or it may still be in flight.  If it's in flight,
                        // the AbortThread call may be waiting for this thread to exit this finally block to exit, so while
                        // spinning waiting for the callback to complete, we also need to call ResetAbortThread in order to
                        // reset the flag the AbortThread call is polling in its waiting loop.
                        SpinWait sw = default;
                        while (!canceler.IsCancelCompleted)
                        {
                            ResetAbortThread();
                            sw.SpinOnce();
                        }
                    }
                }
            }
            catch (ThreadAbortException tae)
            {
                // We don't want to leak ThreadAbortExceptions to user code.  Instead, translate the exception into
                // an OperationCanceledException, preserving stack trace details from the ThreadAbortException in
                // order to aid in diagnostics and debugging.
                OperationCanceledException e = cancellationToken.IsCancellationRequested ? new(cancellationToken) : new();
                if (tae.StackTrace is string stackTrace)
                {
                    ExceptionDispatchInfo.SetRemoteStackTrace(e, stackTrace);
                }
                throw e;
            }
            finally
            {
                // Unmark this thread for recursion detection.
                t_executing = false;

                if (cancellationToken.IsCancellationRequested)
                {
                    // Reset an abort request that may still be pending on this thread.
                    ResetAbortThread();
                }
            }
        }