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); }
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(); } } }