Cancelling a Task is throwing an exception
You are explicitly throwing an Exception on this line:
cancelToken.ThrowIfCancellationRequested();
If you want to gracefully exit the task, then you simply need to get rid of that line.
Typically people use this as a control mechanism to ensure the current processing gets aborted without potentially running any extra code. Also, there is no need to check for cancellation when calling ThrowIfCancellationRequested()
since it is functionally equivalent to:
if (token.IsCancellationRequested)
throw new OperationCanceledException(token);
When using ThrowIfCancellationRequested()
your Task might look more like this:
int CalculatePrime(CancellationToken cancelToken, object digits) {
try{
while(true){
cancelToken.ThrowIfCancellationRequested();
//Long operation here...
}
}
finally{
//Do some cleanup
}
}
Also, Task.Wait(CancellationToken)
will throw an exception if the token was cancelled. To use this method, you will need to wrap your Wait call in a Try...Catch
block.
MSDN: How to Cancel a Task
Some of the above answers read as if ThrowIfCancellationRequested()
would be an option. It is not in this case, because you won't get your resulting last prime. The idiomatic way that "the method you called was cancelled"
is defined for cases when canceling means throwing away any (intermediate) results. If your definition of cancelling is "stop computation and return the last intermediate result" you already left that way.
Discussing the benefits especially in terms of runtime is also quite misleading: The implemented algorithm sucks at runtime. Even a highly optimized cancellation will not do any good.
The easiest optimization would be to unroll this loop and skip some unneccessary cycles:
for(i=2; i <= num/2; i++) {
if((num % i) == 0) {
// num is evenly divisible -- not prime
isprime = false;
factor = i;
}
}
You can
- save (num/2)-1 cycles for every even number, which is slightly less than 50% overall (unrolling),
- save (num/2)-square_root_of(num) cycles for every prime (choose bound according to math of smallest prime factor),
- save at least that much for every non-prime, expect much more savings, e.g. num = 999 finishes with 1 cycle instead of 499 (break, if answer is found) and
- save another 50% of cycles, which is of course 25% overall (choose step according to math of primes, unrolling handles the special case 2).
That accounts to saving a guaranteed minimum of 75% (rough estimation: 90%) of cycles in the inner loop, just by replacing it with:
if ((num % 2) == 0) {
isprime = false;
factor = 2;
} else {
for(i=3; i <= (int)Math.sqrt(num); i+=2) {
if((num % i) == 0) {
// num is evenly divisible -- not prime
isprime = false;
factor = i;
break;
}
}
}
There are much faster algorithms (which I won't discuss because I'm far enough off-topic) but this optimization is quite easy and still proves my point: Don't worry about micro-optimizing runtime when your algorithm is this far from optimal.
I am trying to avoid any exceptions when cancelling.
You shouldn't do that.
Throwing OperationCanceledException
is the idiomatic way that "the method you called was cancelled" is expressed in TPL. Don't fight against that - just expect it.
It's a good thing, because it means that when you've got multiple operations using the same cancellation token, you don't need to pepper your code at every level with checks to see whether or not the method you've just called has actually completed normally or whether it's returned due to cancellation. You could use CancellationToken.IsCancellationRequested
everywhere, but it'll make your code a lot less elegant in the long run.
Note that there are two pieces of code in your example which are throwing an exception - one within the task itself:
cancelToken.ThrowIfCancellationRequested()
and one where you wait for the task to complete:
task.Wait(cancellationToken.Token);
I don't think you really want to be passing the cancellation token into the task.Wait
call, to be honest... that allows other code to cancel your waiting. Given that you know you've just cancelled that token, it's pointless - it's bound to throw an exception, whether the task has actually noticed the cancellation yet or not. Options:
- Use a different cancellation token (so that other code can cancel your wait independently)
- Use a time-out
- Just wait for as long as it takes