public static void DerivedCancellationTokenSource_Negative() { // Test the Dispose path for a class derived from CancellationTokenSource { var disposeTracker = new DisposeTracker(); CancellationTokenSource c = new DerivedCTS(disposeTracker); c.Dispose(); // Dispose() should have prevented the finalizer from running. Give the finalizer a chance to run. If this // results in Dispose(false) getting called, we'll catch the issue. GC.Collect(); GC.WaitForPendingFinalizers(); Assert.Throws<ObjectDisposedException>( () => { // Accessing the Token property should throw an ObjectDisposedException if (c.Token.CanBeCanceled) Assert.True(false, string.Format("DerivedCancellationTokenSource: Accessing the Token property should throw an ObjectDisposedException, but it did not.")); else Assert.True(false, string.Format("DerivedCancellationTokenSource: Accessing the Token property should throw an ObjectDisposedException, but it did not.")); }); } }
public static void DerivedCancellationTokenSource() { // Verify that a derived CTS is functional { CancellationTokenSource c = new DerivedCTS(null); CancellationToken token = c.Token; var task = Task.Factory.StartNew(() => c.Cancel()); task.Wait(); Assert.True(token.IsCancellationRequested, "DerivedCancellationTokenSource: The token should have been cancelled."); } // Verify that callback list on a derived CTS is functional { CancellationTokenSource c = new DerivedCTS(null); CancellationToken token = c.Token; int callbackRan = 0; token.Register(() => Interlocked.Increment(ref callbackRan)); var task = Task.Factory.StartNew(() => c.Cancel()); task.Wait(); SpinWait.SpinUntil(() => callbackRan > 0, 1000); Assert.True(callbackRan == 1, "DerivedCancellationTokenSource: Expected the callback to run once. Instead, it ran " + callbackRan + " times."); } // Test the Dispose path for a class derived from CancellationTokenSource { var disposeTracker = new DisposeTracker(); CancellationTokenSource c = new DerivedCTS(disposeTracker); Assert.True(c.Token.CanBeCanceled, "DerivedCancellationTokenSource: The token should be cancellable."); c.Dispose(); // Dispose() should have prevented the finalizer from running. Give the finalizer a chance to run. If this // results in Dispose(false) getting called, we'll catch the issue. GC.Collect(); GC.WaitForPendingFinalizers(); Assert.True(disposeTracker.DisposeTrueCalled, "DerivedCancellationTokenSource: Dispose(true) should have been called."); Assert.False(disposeTracker.DisposeFalseCalled, "DerivedCancellationTokenSource: Dispose(false) should not have been called."); } // Test the finalization code path for a class derived from CancellationTokenSource { var disposeTracker = new DisposeTracker(); // Since the object is not assigned into a variable, it can be GC'd before the current method terminates. // (This is only an issue in the Debug build) new DerivedCTS(disposeTracker); // Wait until the DerivedCTS object is finalized SpinWait.SpinUntil(() => { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); return disposeTracker.DisposeTrueCalled; }, 500); Assert.False(disposeTracker.DisposeTrueCalled, "DerivedCancellationTokenSource: Dispose(true) should not have been called."); Assert.True(disposeTracker.DisposeFalseCalled, "DerivedCancellationTokenSource: Dispose(false) should have been called."); } // Verify that Dispose(false) is a no-op on the CTS. Dispose(false) should only release any unmanaged resources, and // CTS does not currently hold any unmanaged resources. { var disposeTracker = new DisposeTracker(); DerivedCTS c = new DerivedCTS(disposeTracker); c.DisposeUnmanaged(); // No exception expected - the CancellationTokenSource should be valid Assert.True(c.Token.CanBeCanceled, "DerivedCancellationTokenSource: The token should still be cancellable."); Assert.False(disposeTracker.DisposeTrueCalled, "DerivedCancellationTokenSource: Dispose(true) should not have been called."); Assert.True(disposeTracker.DisposeFalseCalled, "DerivedCancellationTokenSource: Dispose(false) should have run."); } }