Cancelling a Task Is Throwing an Exception

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

Task Cancellation Throwing Exception

This line

token.ThrowIfCancellationRequested();

explicitly throws an exception. What the link was telling you is that if the token of the task matches the token in the OperationCanceledException that has just been thrown, "the Task transitions to the Canceled state (rather than the Faulted state)."

So the bottom line is if you don't want an exception to be thrown when the task is canceled, simply omit that line!

Cancel all async methods if one throws an exception

Because the relevant part of your code is:

try
{
...
await secondTask;
await firstTask;
}
catch(...)
{
source.Cancel();
}

Now while the firstTask is started and has thrown, it is awaited after the secondTask. The exception won't surface in the caller until the task is awaited. And so the catch clause will only execute after secondTask has already completed. The Cancel() is just happening too late.

If you want your firstTask to interrupt the second one you will have to

  1. pass the source into FirstAsync and call Cancel() there. A little ugly.
  2. change the await structure. I think your sample is a little artificial. Use Parallel.Invoke() or something similar and it will happen quite naturally.

cancelling tasks throws exception but task continues

Use Task.Run instead of Task.Factory.StartNew and try to avoid mixing Task and Thread.Sleep. Use Task.Delay. If using Task then the code needs to be async all the way.

Your loop continues because there is nothing to break out of the loop.

A rewrite of the above example with proper syntax would look like this

public class Program {
public static async Task Main(string[] args) {
Console.WriteLine("Hello");
await CancelingTasks2();
Console.WriteLine("Exit");
}

private static async Task CancelingTasks2() {
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

var t = print("printing for ever ...", token);

await Task.Delay(2000);

cts.Cancel();

Console.WriteLine("canceled");
Console.WriteLine("task status " + t.Status);
Console.WriteLine("token IsCancellationRequested " + token.IsCancellationRequested);
}

private static async Task print(string txt, CancellationToken token) {
while (true) {
if (token.IsCancellationRequested)
throw new OperationCanceledException("cancelled on the token", token);
Console.WriteLine(txt);
await Task.Delay(500);
}
}
}

And produce the following output when run

Hello
printing for ever ...
printing for ever ...
printing for ever ...
printing for ever ...
printing for ever ...
canceled
task status WaitingForActivation
token IsCancellationRequested True
Exit

Fiddle

Waiting for multiple tasks when some might be cancelled

I think the problem is the TaskContinuationOption you pass. In your example you use OnlyOnCanceled. So it only continues with that if a task was cancelled.

I'm not sure what the desired behaviour is when a task was cancelled. If you only want to proceed when none of them was cancelled, you could use NotOnCanceled. If you want to proceed in either case, with cancelled tasks or not, then you could for example use NotOnFaulted, since cancellation is not regarded as fault.

Canceling task if a parallel task throw an exception

Solved with @noobed comment.

try
{
//fetch Entity1 results into entity1List
}
catch
{
cts.Cancel();
}

In case of general exception it will be catch, and the cts can be called with cancel.

Async Tasks, Cancellation, and Exceptions

I always recommend people read the Cancellation in Managed Threads docs. It's not quite complete; like most MSDN docs, it tells you what you can do, not what you should do. But it's definitely more clear than the dotnet docs on cancellation.

The example shows the basic usage

First, it's important to note that the cancellation in your example code only cancels the task - it does not cancel the underlying operation. I strongly recommend that you do not do this.

If you want to cancel the operation, then you would need to update RunFoo to take a CancellationToken (see below for how it should use it):

public Task Run(IFoo foo, CancellationToken token = default(CancellationToken)) {
var tcs = new TaskCompletionSource<object>();

// Regular finish handler
EventHandler<AsyncCompletedEventArgs> callback = (sender, args) =>
{
if (args.Cancelled)
{
tcs.TrySetCanceled(token);
CleanupFoo(foo);
}
else
tcs.TrySetResult(null);
};

RunFoo(foo, token, callback);
return tcs.Task;
}

If you can't cancel foo, then don't have your API support cancellation at all:

public Task Run(IFoo foo) {
var tcs = new TaskCompletionSource<object>();

// Regular finish handler
EventHandler<EventArgs> callback = (sender, args) => tcs.TrySetResult(null);

RunFoo(foo, callback);
return tcs.Task;
}

Callers can then perform a cancelable wait on the task, which is a much more appropriate code technique for this scenario (since it is the wait that is canceled, not the operation represented by the task). Performing a "cancelable wait" can be done via my AsyncEx.Tasks library, or you could write your own equivalent extension method.

The documentation says either just returning and having the task status switch to RanToCompletion, or throwing an OperationCanceledException (which results in the task's result being Canceled) is fine.

Yeah, those docs are misleading. First, please don't just return; your method would complete the task successfully - indicating the operation completed successfully - when in fact the operation did not complete successfully. This may work for some code, but is certainly not a good idea in general.

Normally, the proper way to respond to a CancellationToken is to either:

  • Periodically call ThrowIfCancellationRequested. This option is better for CPU-bound code.
  • Register a cancellation callback via Register. This option is better for I/O-bound code. Note that registrations must be disposed!

In your particular case, you have an unusual situation. In your case, I would take a third approach:

  • In your "per-frame work", check token.IsCancellationRequested; if it is requested, then raise the callback event with AsyncCompletedEventArgs.Cancelled set to true.

This is logically equivalent to the first proper way (periodically calling ThrowIfCancellationRequested), catching the exception, and translating it into an event notification. Just without the exception.

I always get a TaskCanceledException if I await the task returned. My guess would be that this is normal behaviour (I hope it is) and that I'm expected to wrap a try/catch around the call when I want to use cancellation.

The proper consuming code for a task that can be canceled is to wrap the await in a try/catch and catch OperationCanceledException. For various reasons (many historical), some APIs will cause OperationCanceledException and some will cause TaskCanceledException. Since TaskCanceledException derives from OperationCanceledException, consuming code can just catch the more general exception.

But I guess if a user wants to distinguish between a task that finished normally and one that was canceled, the [cancellation exception] is pretty much a side-effect of ensuring that, right?

That's the accepted pattern, yes.

Documentation suggests that any exceptions, even those that pertain to cancellation, are wrapped in an AggregateException by the TPL.

This is only true if your code synchronously blocks on a task. Which it should really avoid doing in the first place. So the docs are definitely misleading again.

However, in my tests I'd always get the TaskCanceledException directly, without any wrapper.

await avoids the AggregateException wrapper.

Update for comment explaining CleanupFoo is a cancellation method.

I'd first recommend trying to use CancellationToken directly within the code initiated by RunFoo; that approach would almost certainly be easier.

However, if you must use CleanupFoo for cancellation, then you'll need to Register it. You'll need to dispose that registration, and the easiest way to do this may actually be to split it into two different methods:

private Task DoRun(IFoo foo) {
var tcs = new TaskCompletionSource<object>();

// Regular finish handler
EventHandler<EventArgs> callback = (sender, args) => tcs.TrySetResult(null);

RunFoo(foo, callback);
return tcs.Task;
}

public async Task Run(IFoo foo, CancellationToken token = default(CancellationToken)) {
var tcs = new TaskCompletionSource<object>();
using (token.Register(() =>
{
tcs.TrySetCanceled(token);
CleanupFoo();
});
{
var task = DoRun(foo);
try
{
await task;
tcs.TrySetResult(null);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
await tcs.Task;
}

Properly coordinating and propagating the results - while preventing resource leaks - is quite awkward. If your code could use CancellationToken directly, it would be much cleaner.

How to properly cancel Task.WhenAll and throw the first exception?

You shouldn't use ContinueWith. The correct answer is to introduce another "higher-level" async method instead of attaching a continuation to each task:

private async Task DoSomethingWithCancel(CancellationTokenSource cts)
{
try
{
await DoSomethingAsync(cts.Token).ConfigureAwait(false);
}
catch
{
cts.Cancel();
throw;
}
}

var cts = new CancellationTokenSource();
try
{
var tasks = new Task[] { DoSomethingWithCancel(cts), ... };
await Task.WhenAll(tasks).ConfigureAwait(false);
}
catch (SpecificException)
{
...
}


Related Topics



Leave a reply



Submit