2

If I call cancellationTokenSource.Cancel within the task associated with the cancellation token, the OperationCancelledException is correctly thrown, however, task.IsCanceled is NOT always updated and set to true, as would be expected.

The problem can be quickly demonstrated with the following nUnit test:

var cancellationTokenSource = new CancellationTokenSource(); Task task = Task.Factory.StartNew(() => { cancellationTokenSource.Cancel(); cancellationTokenSource.Token.ThrowIfCancellationRequested(); }, cancellationTokenSource.Token); try { task.Wait(cancellationTokenSource.Token); } catch (OperationCanceledException) { } if (task.IsCanceled) { Assert.Pass(); } else { Assert.Fail(); } 

When I run this test, the test passes, however, when I DEBUG this test (using the Resharper test runner), the test fails.

I don't think this has anything to do with Resharper, I think Resharper just may be creating some conditions that perhaps expose an issue in .Net. Or, maybe I am just doing something completely wrong... Any insights?

7
  • Works for me in a console app... have you put breakpoints in to check that Cancel is being called, for example? Commented Jan 13, 2015 at 18:08
  • Yes, I've stepped through it. Ultimately, cancellationTokenSource.IsCancellationRequested is set to true, but task.IsCanceled is still set to false. It is boggling my mind. Commented Jan 13, 2015 at 18:12
  • So what is task.Status? Commented Jan 13, 2015 at 18:12
  • @Mikeyg36 Typically this would happen if you failed to pass the cancellation token to the call to StartNew, and only used it in the body. Commented Jan 13, 2015 at 18:13
  • This thing is acting like schrodinger's cat. If I add Console.WriteLine("task.Status: {0}", task.Status); right before the if-block, the test passes. But if I remove that call, the test fails. Commented Jan 13, 2015 at 18:16

1 Answer 1

6

Don't use the cancellation token when waiting on the Task. It's causing Wait to throw and move on to the assert before the task's status gets set.

The two things are happening in parallel, so it's actually a race condition as to whether or not it happens, hence the issues you've had trying to replicate the issues and the correct behavior when debugging.

Sign up to request clarification or add additional context in comments.

7 Comments

But isn't the exception only supposed to be thrown when I call cancellationTokenSource.Token.ThrowIfCancellationRequested(), which only happens once?
@Mikeyg36 task.Wait(cancellationTokenSource.Token); throws if the token is cancelled before the task is considered completed. It also throws if the task being waited on is faulted/cancelled.
Ah, so task.wait is throwing as soon cancellationTokenSource.Cancel() is called, but then the task continues execution asynchronously until cancellationTokenSource.Token.ThrowIfCancellationRequested(), at which point it itself throws an exception. Is that what you are saying?
@Mikeyg36 Yes. The exact timings can actually vary a bit. The point is simply that Wait can continue on before the task is completed. The exact possible interweivings are numerous. The Wait moving on and the task being marked as cancelled both happen at some indeterminate amount of time after the token is cancelled. If the Wait reaches IsCancelled first, your test fails. If it reaches it second, your test passes.
But isn't this a correct use-case? Aren't you supposed to use the cancellation token exactly like this: pass it into a task, Wait for it, then check whether it is canceled or not. To say that Wait can continue on before the task is completed seems to defeat the main invariant of Wait, no?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.