Task Cancellation Throwing Exception - c#

So, according to an answer to this post :
2) If the body of the task is also monitoring the cancellation token
and throws an OperationCanceledException containing that token (which
is what ThrowIfCancellationRequested does), then when the task sees
that OCE, it checks whether the OCE's token matches the Task's token.
If it does, that exception is viewed as an acknowledgement of
cooperative cancellation and the Task transitions to the Canceled
state (rather than the Faulted state).
From this I understood that by passing a token to the constructor of the task and then calling that same token's ThrowIfCancellationRequested() method, the task would in fact terminate peacefully, without me having to catch the OperationCanceledException explicitly.
However as it turns out, an exception is thrown, so I believe I may have misunderstood the mechanic.
My code:
public void AI()
{
IsBusy = true;
var token = stopGameCancellationTokenSource.Token;
var workTask = new Task(() => aiWork(token), token);
workTask.Start();
workTask.ContinueWith(task => { IsBusy = false; });
}
private void aiWork(CancellationToken token)
{
while ( true)
{
//Some computation being done here
token.ThrowIfCancellationRequested(); //Exception is thrown here, I thought it wouldn't
//More computation here, which I don't want to happen if cancellation has benn requested
}
}

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!

In addition to the explanation in #JurgenCamilleri answer of why you are getting the error, what you likely intended to do was loop until cancellation was requested. You can do that by changing your code to something like this:
private void aiWork(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
//Some computation being done here
if (token.IsCancellationRequested)
break; // need to cancel
//More computation here, which I don't want to happen if cancellation has been requested
}
}

As the name of the method suggests, ThrowIfCancellationRequested will throw an exception (OperationCanceledException) if a cancelletion was requested.
If you really don't want an exception to be thrown, you can check if token.IsCancellationRequested is true and, in this case, exit your function.
However, I'd recommand sticking with token.ThrowIfCancellationRequested() unless you got good reasons not to.

Related

Sould I catch OperationCanceledException in OnParametersSetAsync/LifeCycle methods when cancelling Tasks inside it?

I have a blazor component (aka razor component, aka MyPage.razor file) with a #page directive. I'll call the thing/object "page" from here on.
It needs to await a Task (HTTP request simulated with Task.Delay) inside its OnParametersSetAsync().
When the page is left (user navigates somehwere else), awaited Tasks must be cancelled, so that there is no ping-ping (with data access) when thew new page is loaded and the old pages's Task finally finishes delayed. This is the reason for Dipose().
Since the Framework calls OnParametersSetAsync() rather than my own code, I'm not sure if I should let the OperationCanceledException simply bubble up (at finally probably be ignore afaik as the master of async said) - or if I should catch it and return gracefully from OnParametersSetAsync().
Is Blazor handeling cancellation from LifeCycle methods properly or is this the recommended way? Sadly the docu is very sparse. The example offers an button event handler, but IDK if that also counts for LifeCycle methods. But at least it seems it doesnt hurt the event handler (LongRunningWork), that it is not catched in user code.
I have testes both scenarios, and it seems either way, both work seemingly...
What I've noticed, is that even if the async Task OnParametersSetAsync() completes but another page is already active the the Task belongs to an already disosed page, no children LifeCycle methods are called anymore. The big question here is, is it "only" the C# user code in the remaining body of OnParametersSetAsync() that is executed delayed after the page has already been disposed - or will the successful completion of OnParametersSetAsync() trigger some other framework methods/events, even if the page has already been disposed, resulting in highly unpredictable behaviour? I'd also like to know that answer.
In any case, even if this would not cause problems, cancellation might still be important, so that at the end of the user code in OnParametersSetAsync() does not do any operations (e.g. on some data in some injected service or sth like that) that shouldn't be done anymore after disposing. So what's the right way?
Edit: Stephen said:
Ideally, you want to observe all your Task exceptions.
which is not possible, since OnParametersSetAsync() is called from the framework not from the user code, so I can't observe it inside the caller!
// MyPage.razor
<Child SomePara=#SomePara></Child>
//#code{
// ...
//private CancellationTokenSource cts = new();
//object SomePara = new();
// catch it?
protected override async Task OnParametersSetAsync()
{
Debug.WriteLine($"OnParametersSetAsync Start");
// sync stuff...
// async stuff:
try
{
await Task.Delay(5000, cts.Token);
await UnknownExternalTaskIWantToCancelAsync(cts.Token);
}
catch (Exception)
{
return; //??
throw; //??
}
//when cancel is requested, stop here, this component is being disposed and should do as little as possible, especially nothing async and should't inform children to render
//when cancel is requested, while above Tasks are awaited, all below code MUST NOT run
// ??
//cts.Token.ThrowIfCancellationRequested();
Debug.WriteLine($"OnParametersSetAsync End");
// stuff I don't want do be done after cancelled
}
// let it bubble up?
protected override async Task OnParametersSetAsync()
{
Debug.WriteLine($"OnParametersSetAsync Start");
// sync stuff...
// async stuff:
await Task.Delay(5000, cts.Token);
await UnknownExternalTaskIWantToCancelAsync(cts.Token);
//when cancel is requested, stop here, this Razor component is being disposed and should do as little as possible, especially nothing async and should't inform children to render
//when cancel is requested, while above Tasks are awaited, all below code MUST NOT run
// ??
//cts.Token.ThrowIfCancellationRequested();
Debug.WriteLine($"OnParametersSetAsync End");
// stuff I don't want do be done after cancelled
}
public void Dispose()
{
Debug.WriteLine($"Disposing");
cts.Cancel();
cts.Dispose();
}
async Task UnknownExternalTaskIWantToCancelAsync(CancellationToken cts)
{
//This could be a HTTP call or some other Task.
Debug.WriteLine($" . . . . . START.");
await Task.Delay(10000, cts);
Debug.WriteLine($" . . . . . FINISHED.");
}
//}
One could imagine also a pretty hacky idea, but thats prolly bad:
// Hacky option ?????
bool isCancelled = false;
protected override async Task OnParametersSetAsync()
{
Debug.WriteLine($"OnParametersSetAsync Start");
// sync stuff...
// async stuff:
await Task.Delay(5000);
await UnknownExternalTaskIWantToCancelAsync(cts.Token);
//when cancel is requested, stop here, this Razor component is being disposed and should do as little as possible, especially nothing async and should't inform children to render
if (isCancelled)
{
return;
}
Debug.WriteLine($"OnParametersSetAsync End");
// stuff I don't want do be done after cancelled
}
public void Dispose()
{
Debug.WriteLine($"Disposing");
isCancelled = true ;
}
Update:
In the link I provided above, the official docs DON'T CATCH the exception in LongRunningWork !! Which leads to a Console msg:
Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll
A task was canceled.
Question: Is it ok to do it exactly like that with LifeCycle methods, specifically OnParametersSetAsync() without wrapping a try-catch block around the Task to catch the OperationCanceledException which is legit way a CTS cancels the Task. As the nature of an exception, it bubbles up until catched. OperationCanceledException are special and won't crash the App, but is it ok to let it bubble up in a Blazor LifeCycle method?? LongRunningWork is NOT a Blazor LifeCycle method, but only a event handler for a button click event. Is it ok to treat a LifeCycle methods the same way in this regard??
Update:
I've read several posts about Task cancellation aswell as the official docs, but none answers the specific case for the Blazor LifeCycle methods, like OnParametersSetAsync.
Links:
Regarding asynchronous Task, why is a Wait() required to catch OperationCanceledException?
How to properly cancel Task and catch OperationCanceledException?
How to cancel a Task using CancellationToken?
Elegantly handle task cancellation
How to Correctly Cancel a TPL Task with Continuation
Cancelling a Task is throwing an exception
Please try to refer to make code examples of "catch it?" or "let it bubble up?" in your answers, thanks very much.
You can use CancellationTokenSource.Object that creates a cancellation token, and also issues the cancellation request for all copies of that token. Below is a work demo, you can refer to it.
protected override async Task OnParametersSetAsync()
{ // Create the token source.
CancellationTokenSource cts = new CancellationTokenSource();
// Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);
// Request cancellation.
cts.Cancel();
Console.WriteLine("Cancellation set in token source...");
Thread.Sleep(2500);
// Cancellation should have happened, so call Dispose.
cts.Dispose();
static void DoSomeWork(object obj)
{
CancellationToken token = (CancellationToken)obj;
for (int i = 0; i < 100000; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("In iteration {0}, cancellation has been requested...",
i + 1);
// Perform cleanup if necessary.
//...
// Terminate the operation.
break;
}
// Simulate some work.
Console.WriteLine(i.ToString());
Thread.Sleep(1000);
}
}
}
result:
If remove cts.Cancel(); ,you will see it will go on.
Read Cancellation in Managed Threads to know more.

When should I call CancellationToken.ThrowIfCancellationRequested?

I developed a C# based Windows Service which runs all of its logic in several different tasks.
To allow the service to shutdown gracefully when it is being stopped, I am using a CancellationToken which is passed to any function that accepts one (mostly from 3rd party libraries which I am using) in order to abort processing before completion.
I noticed that none of those functions throw an OperationCanceledException when the cancellation is requested while the function is being called, so my application simply continues executing until I call ThrowIfCancellationRequested() somewhere else later in my code. Am I supposed to manually call ThrowIfCancellationRequested() after calling every single of those functions to make sure that the tasks stop as soon as possible, or when exactly am I supposed to call ThrowIfCancellationRequested() in my own code?
Yes, you are supposed to call ThrowIfCancellationRequested() manually, in the appropriate places in your code (where appropriate is determined by you as a programmer).
Consider the following example of a simple job processing function that reads jobs from a queue and does stuff with them. The comments illustrate the sort of thinking the developer might go through when deciding whether to check for cancellation.
Note also that you are right - the standard framework functions that accept a token will not throw a cancellation exception - they will simply return early, so you have to check for cancellation yourself.
public async Task DoWork(CancellationToken token)
{
while(true)
{
// It is safe to check the token here, as we have not started any work
token.ThrowIfCancellationRequested();
var nextJob = GetNextJob();
// We can check the token here, because we have not
// made any changes to the system.
token.ThrowIfCancellationRequested();
var jobInfo = httpClient.Get($"job/info/{nextJob.Id}", token);
// We can check the token here, because we have not
// made any changes to the system.
// Note that HttpClient won't throw an exception
// if the token is cancelled - it will just return early,
// so we must check for cancellation ourselves.
token.ThrowIfCancellationRequested();
// The following code is a critical section - we are going to start
// modifying various databases and things, so don't check for
// cancellation until we have done it all.
ModifySystem1(nextJob);
ModifySystem2(nextJob);
ModifySystem3(nextJob);
// We *could* check for cancellation here as it is safe, but since
// we have already done all the required work *and* marking a job
// as complete is very fast, there is not a lot of point.
MarkJobAsCompleted(nextJob);
}
}
Finally, you might not want to leak cancellation exceptions from your code, because they aren't "real" exceptions - they are expected to occur whenever someone stops your service.
You can catch the exception with an exception filter like so:
public async Task DoWork(CancellationToken token)
{
try
{
while(true)
{
// Do job processing
}
}
catch (OperationCanceledException e) when (e.CancellationToken == token)
{
Log.Info("Operation cancelled because service is shutting down.");
}
catch (Exception e)
{
Log.Error(e, "Ok - this is actually a real exception. Oh dear.");
}
}

Waiting for multiple tasks when some might be cancelled

I'm trying to wait for multiple tasks, and I expect that some might be cancelled. I'm using this question as a source of inspiration to have WhenAll handle cancelled tasks accordingly...
However, if none of the tasks ever get cancelled, this in turn throws an exception too!
var source = new CancellationTokenSource();
var task1 = Task.Delay(100, source.Token);
source.Cancel();
var task2 = Task.Delay(300);
await Task.WhenAll(task1, task2).ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnCanceled);
In the example above if the source.Cancel() is NOT executed, then the final await will throw an exception. I could fix that by removing the ContinueWith from the final line, but then if a task IS cancelled, the WhenAll will fail with the same error.
What's the correct course of action to wait for a list of tasks when some of them might (but don't have to) be cancelled?
The ContinueWith is a primitive method, generally unsuitable for application code. Mixing it with await is even more inadvisable, since then you use two different mechanisms having slightly different semantics to accomplish the same goal. Your example implies that you want to ignore exceptions caused by the cancellation of the CancellationTokenSource. My advice is to just catch these exceptions and ignore them.
var cts = new CancellationTokenSource(100);
var task1 = Task.Delay(200, cts.Token);
var task2 = Task.Delay(300);
try
{
await Task.WhenAll(task1, task2);
}
catch (OperationCanceledException) { } // Ignore cancellation exceptions
You can be even more specific by using the when contextual keyword, in order to avoid ignoring exceptions originated by unknown CancellationTokenSources:
catch (OperationCanceledException ex) when (ex.CancellationToken == cts.Token) { }
This may create different problems though, because a method may wrap the supplied CancellationToken into a linked CancellationTokenSource, in which case the catch/when may not handle an OperationCanceledException that was originated by a known token.
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.

C# - Whats wrong with my CancellationToken?

Pls look at this code, running on .Net Core 2.0:
var src = new CancellationTokenSource(5000);
var token = src.Token;
var responseTask = await Task.Factory.StartNew(async () =>
{
//Uncomment bellow to reproduce locally
//await Task.Delay(60000);
return await BadSDK.OperationThatDoesNotReceiveCancellationToken();//takes around 1 min
}, token);
var response = await responseTask;
My issue here is that the await is always awaiting the very long sdk call, instead of waiting the 5sec.
What am I doing wrong? Where is my understanding wrong?
Edit1: This code behaves as expected:
var src = new CancellationTokenSource(5000);
var token = src.Token;
var responseTask = Task.Factory.StartNew(() =>
{
var task = BadSDK.OperationThatDoesNotReceiveCancellationToken();
task.Wait(token);
cancellationToken.ThrowIfCancellationRequested();
return task.Result;
}, token);
meaning, after 5 seconds, a exception is thrown
The problem is that the cancellation token pattern expects the task to check the token and exit or throw an error when the token has expired. Well written tasks will periodically check to see if cancellation is canceled and then the task can do any cleanup necessary and return gracefully or throw an error.
As you demonstrated, BadSDK.OperationThatDoesNotReceiveCancellationToken doesn't accept a CancellationToken and thus won't take any action based on the token. It doesn't matter if the token automatically requests cancellation via a timeout, or if the request is issued in some other matter. The simple fact is that BadSDK.OperationThatDoesNotReceiveCancellationToken simply isn't checking it.
In your Edit1, the CancellationToken is passed to Wait which does watch the token and will exit when cancellation is requested. This does not mean however that task was killed or stopped, it only stopped waiting for it. Depending on what you intend, this may or may not do what you want. While it does return after 5 seconds, the task will still continue running. You could even wait on it again. It may be possible to then kill the task but in practice, it can be a very bad thing to do (See Is it possible to abort a Task like aborting a Thread (Thread.Abort method)?)
The StartNew overload you used is a source of endless confusion. Doubly so, since its type is actually StartNew<Task<T>> (a nested task) rather than StartNew<T>.
A token on its own does nothing. Some code somewhere has to check the token, and throw an exception to exit the Task.
Official documentation follows this pattern:
var tokenSource = new CancellationTokenSource();
var ct = tokenSource.Token;
var task = Task.Run(() =>
{
while (...)
{
if (ct.IsCancellationRequested)
{
// cleanup your resources before throwing
ct.ThrowIfCancellationRequested();
}
}
}, ct); // Pass same token to Task.Run
But if you're anyway checking the token, and maybe throwing an exception why do you need to pass in the token in the first place, and then use the same token inside the closure?
The reason is that the token you pass in is what's used to move the Task to a cancelled state.
When a task instance observes an OperationCanceledException thrown by
user code, it compares the exception's token to its associated token
(the one that was passed to the API that created the Task). If they
are the same and the token's IsCancellationRequested property returns
true, the task interprets this as acknowledging cancellation and
transitions to the Canceled state.
P.S.
If you're on .Net Core or 4.5+, Task.Run is preferred to the factory approach.
You are passing the task the cancelation token token, but you don't specify what to do with the token within the async method.
You'd probably want to add token.ThrowIfCancellationRequested(); in the method, perhaps conditioned by token.IsCancellationRequested. This way the task will be canceled if src .Cancel() is called.

Is there any other way to set Task.Status to Cancelled

Ok, so I understand how to do Task cancellations using CancellationTokenSource. it appears to me that the Task type "kind of" handles this exception automatically - it sets the Task's Status to Cancelled.
Now you still actually have to handle the OperationCancelledException. Otherwise the exception bubbles up to Application.UnhandledException. The Task itself kind of recognizes it and does some handling internally, but you still need to wrap the calling code in a try block to avoid the unhandled exception. Sometimes, this seems like unnecessary code. If the user presses cancel, then cancel the Task (obviously the task itself needs to handle it too). I don't feel like there needs to be any other code requirement. Simply check the Status property for the completion status of the task.
Is there any specific reason for this from a language design point of view? Is there any other way to set the Status property to cancelled?
You can set a Task's status to cancelled without a CancellationToken if you create it using TaskCompletionSource
var tcs = new TaskCompletionSource();
var task = tcs.Task;
tcs.SetCancelled();
Other than that you can only cancel a running Task with a CancellationToken
You only need to wrap the calling code in a try/catch block where you're asking for the result, or waiting for the task to complete - those are the situations in which the exception is thrown. The code creating the task won't throw that exception, for example.
It's not clear what the alternative would be - for example:
string x = await GetTaskReturningString();
Here we never have a variable referring to the task, so we can't explicitly check the status. We'd have to use:
var task = GetTaskReturningString();
string x = await task;
if (task.Status == TaskStatus.Canceled)
{
...
}
... which is not only less convenient, but also moves the handling of the "something happened" code into the middle of the normal success path.
Additionally, by handling cancellation with an exception, if you have several operations, you can put all the handling in one catch block instead of checking each task separately:
try
{
var x = await GetFirstTask();
var y = await GetSecondTask(x);
}
catch (OperationCanceledException e)
{
// We don't care which was canceled
}
The same argument applies for handling cancellation in one place wherever in the stack the first cancellation occurred - if you have a deep stack of async methods, cancellation in the deepest method will result in the top-most task being canceled, just like normal exception propagation.

Categories

Resources