How to properly cancel Task and catch OperationCanceledException? - c#

This question has already been asked but I still don't get it... I'm trying to cancel a task but when I include ThrowIfCancellationRequested(), it is not catched and if I don't include it, the GUI freezes...
Here is my code that run without the ThrowIfCancellationRequested() but freeze the GUI:
public void StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
processThread = Task.Factory.StartNew(() =>
{
while (true)
{
//Do some work with myvariable
if (token.IsCancellationRequested)
{
break;
}
}
}, token);
}
And the code with the ThrowIfCancellationRequested() that is not catched, the debugger stops on the line token.ThrowIfCancellationRequested():
public void StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken tokenDispatcher = tokenSource.Token;
processThread = Task.Factory.StartNew(() =>
{
try
{
while (true)
{
//Do some work with myvariable
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
break;
}
}
}
catch (AggregateException ae)
{
if (ae.InnerException is OperationCanceledException)
{
Console.WriteLine("Process Thread Cancelled");
}
else
{
Console.WriteLine("ERROR");
}
}
}, token);
}
What am I doing wrong?

The ThrowIfCancellationRequested is the proper way to cancel the task. What you need to understand is that there will be no exception when you cancel a Task. Only the Status of the task will be set to Canceled. The try catch block you have inside the task will do nothing.
To get an exception you need to await the Task in an async method or use the Wait method to wait for the finish of the task. Using the Wait method is not recommended because you are blocking the current thread until the task finishes.
Here is a suggestion how you could implement this from your code.
public static Task StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
return Task.Factory.StartNew(() =>
{
while (true)
{
//Do some work with myvariable
token.ThrowIfCancellationRequested();
}
}, token);
}
public async Task ExecuteProcess(double myvariable)
{
try
{
await StartProcess(myvariable);
}
catch (OperationCanceledException ex)
{
Console.WriteLine("Process Thread Canceled");
}
catch (Exception ex)
{
Console.WriteLine("ERROR");
}
}
The ExecuteProcess method demonstrates how you need to call your function to get the exception. This way you also prevent the GUI from blocking if you call the method from the UI thread.
I recommend you read the documentation on asynchronous programming to get more understanding on how it works.

Related

Downcasting from a nested task (Task<Task>) to a outer task or awaiting a nested task is occurred. UnWrap() is suggested

Running is giving the following warning from AsyncFixer Visual Studio Extension. How do I use UnWrap with it?
AsyncFixer05: Downcasting from a nested task (Task) to a outer task or awaiting a nested task is occurred. UnWrap() is suggested.
public class Client
{
internal Task Running { get; private set; } = Task.CompletedTask;
public async Task StartAsync()
{
DoDisposeChecks();
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
if (_cts == null)
{
_cts = new CancellationTokenSource();
Running = Task.Factory.StartNew(async () =>
{
_logger.LogTrace("Connection task started: {Url}", _url);
try
{
await HandleConnection().ConfigureAwait(false);
}
catch (Exception e)
{
_logger.LogError(e, "Error in connection task: {Url}: ", _url);
}
_logger.LogTrace("Connection task ended: {Url}", _url);
}, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
finally
{
_semaphore.Release();
}
}
}

TaskCanceledException: Cancel or timeout?

I only want to use CancellationTokenSource for timeout and cancellation handling.
How to distinguish if a TaskCanceledException occured due to a timeout or due to manual cancellation?
Here is a simplified example. In the real program I neither know if CancellationTokenSource .CancelAfter() was used nor if someone called CancellationTokenSource.Cancel()
static CancellationTokenSource cts = new CancellationTokenSource();
static void Main(string[] args)
{
Task.Run(async () =>
{
try
{
await SomeClass.DoSomething(cts.Token);
}
catch (TaskCanceledException ex)
{
//How to find out if the exception occured due to timeout or a call to cts.Cancel()
}
});
while (true)
{
Thread.Sleep(100);
if (someCondition)
cts.Cancel();
}
}
public class SomeClass
{
public static async Task DoSomething(CancellationToken ct)
{
using (var innerCts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
innerCts.CancelAfter(1000);
//Simulate some operation
await Task.Delay(10000, innerCts.Token);
}
}
}
thanks
Tom
AFAIK this is the most commonly used pattern:
Task.Run(async () =>
{
try
{
await SomeClass.DoSomething(cts.Token);
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
// cts cancellation occurred
}
catch (OperationCanceledException)
{
// Timeout occurred
}
});
Another idea is to change the implementation of the SomeClass.DoSomething method, assuming that you are allowed to do it, so that in case of timeout it throws a TimeoutException instead of an OperationCanceledException.
public static async Task DoSomething(CancellationToken cancellationToken)
{
using var innerCts = new CancellationTokenSource(millisecondsDelay: 1000);
using var linkedCts = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken, innerCts.Token);
try
{
// Simulate some operation
await Task.Delay(10000, linkedCts.Token);
}
catch (OperationCanceledException) when (innerCts.IsCancellationRequested)
{
throw new TimeoutException();
}
}

CancellationToken not causing OperationCanceledException to be thrown

I want to restart a Task in case it is already running using a CancellationToken. Here is my code:
CancellationTokenSource cancellationTokenSource;
CancellationToken cancellationToken;
private void OnButtonCLick(object sender, RoutedEventArgs e)
{
cancellationTokenSource?.Cancel();
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
Task.Run(() =>
{
try
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("test");
// ... calculation lasting few milliseconds ...
}
}
catch (OperationCanceledException ex) { }
}, cancellationToken);
}
For some reason the OperationCanceledException does not get thrown when calling ThrowIfCancellationRequested() gets called. I compared it to other examples but don't see a reason why this does not work. What am I doing wrong here?
Your task is reading the cancellationToken field, which will point to the new (not yet cancelled) token. Instead, you must make sure to keep using the one that was originally given to your task:
var token = cancellationToken;
Task.Run(() =>
{
try
{
while (true)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("test");
// ... calculation lasting few milliseconds ...
}
}
catch (OperationCanceledException ex) { }
}, token);

Simple general exception handling in async code without boilerplate

We start using CancellationToken in out app a lot, so we have to change exception handling correspondingly:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await DoJob(cts.Token);
Console.WriteLine("Successfully finished");
}
private static async Task DoJob(CancellationToken ct)
{
try
{
await Task.Delay(1000, ct);
}
catch (Exception e) when(!(e is OperationCanceledException))
{
Console.WriteLine("Do cleanup in case of error.");
}
}
}
The idea behind this code is that if someone use catch(Exception e) (please do not blame me for this) and forgot to exclude CancellationToken, an error handling is executed, for example, there is a log that operation failed. But it is not true, id doesn't fail, it just has been canceled. And cancellation should be handled differently then failure.
It seems to me like a big boilerplate to write practically in every general catch
catch (Exception e) when(!(e is OperationCanceledException))
Is there some more robust solution with less boilerplate?
You could create a method that accepts a Func<Task> and catches the exception(s), e.g.:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await GeneralDoJobAndCatchException(() => DoJob(cts.Token));
Console.WriteLine("Successfully finished");
}
private static async Task GeneralDoJobAndCatchException(Func<Task> func)
{
try
{
await func();
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Console.WriteLine("Do error handling");
}
}
private static async Task DoJob(CancellationToken ct)
{
await Task.Delay(1000, ct);
}
}
We're having the exact same problem. Mainly there is a while-loop that checks for the CancellationToken but you've to catch this exception.
We created the following extension method:
public static async Task<TaskStatus> HideCancellationException(this Task task)
{
try
{
await task;
return task.Status;
}
catch (OperationCanceledException)
{
return TaskStatus.Canceled;
}
}
Having this extension method allows to change this code:
while (!cancellationToken.IsCancellationRequested)
{
// do stuff here...
try
{
await Task.Delay(..., cancellationToken);
}
catch (OperationCanceledException)
{
// expected
}
}
to something like that:
while (!cancellationToken.IsCancellationRequested)
{
// Do stuff here.
await Task.Delay(..., cancellationToken).HideCancellationException();
}
Keep in mind that there is explicitly no overload for Task<T> because the return value in case of cancellation is default. You can't distinguish between default as normal task result and default as result of cancellation. In that case it's better to catch the exception.
You could get rid of the try-catch block altogether by awaiting indirectly with Task.WhenAny, and then querying the status of the completed task:
private static async Task DoJob(CancellationToken ct)
{
var completedTask = await Task.WhenAny(Task.Delay(1000, ct));
if (completedTask.IsFaulted)
{
Console.WriteLine("Error: " + completedTask.Exception.InnerException);
}
else if (completedTask.IsCanceled)
{
// Do nothing
}
else // Success
{
// Do nothing
}
}

Does cancelling a CancellationToken cause a CancellationToken Exception?

I have this code and I would like to get some clarification on the use of the CancellationToken.
I read this question about the difference between using a cancellation token and a flag:
Difference between CancellationTokenSource and exit flag for Task loop exit
One thing I noticed is that it mentions nothing about Exceptions. So here's my question. If the Disappearing() method is called then will this cause a TaskCanceledException() to occur and would this be a good reason to use the CancellationToken instead of a flag?
public partial class PhrasesFrame : Frame
{
CancellationTokenSource cts = new CancellationTokenSource();
public PhrasesFrame(PhrasesPage phrasesPage)
{
Device.BeginInvokeOnMainThread(() => ShowCards(cts.Token).ContinueWith((arg) => { }));
}
public void Disappearing()
{
cts.Cancel();
}
public async Task ShowCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await PickCard();
}
}
public async Task PickCard()
{
await ShowCard();
}
private async Task ShowCard()
{
await ShowPhrase();
await ShowDetail();
}
private async Task ShowPhrase()
{
while (App.pauseCard || timer1Seconds > 0)
{
try
{
await Task.Delay(1000, tokenSource1.Token);
}
catch (TaskCanceledException)
{
// do action
break;
}
}
CancellationTokenSource.Cancel by itself doesn't throw such exception, but it "moves" all related cancellation tokens to cancelled state. When some code notifies that cancellation token is now in cancelled state - it might throw such exception. If look at your example, this part will not throw such exception:
public async Task ShowCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await PickCard();
}
}
Because you just don't throw it in this block. If however you instead did something like this:
public async Task ShowCards(CancellationToken ct)
{
while (true)
{
ct.ThrowIfCancellationRequested();
await PickCard();
}
}
Then exception will be thrown, because, well, you throw it almost explicitly.
Now if look at another method from your example:
private async Task ShowPhrase()
{
while (App.pauseCard || timer1Seconds > 0)
{
try
{
await Task.Delay(1000, tokenSource1.Token);
}
catch (TaskCanceledException)
{
// do action
break;
}
}
}
If you were awaiting Task.Delay(1000, tokenSource1.Token); and then cancel tokenSource1 - then TaskCancelledException will indeed be thrown immediately, without waiting for the whole duration of Task.Delay. This is something you cannot easily achieve with just a boolean flag. If you used Thread.Sleep(1000) and boolean flag - change to that flag won't be noticed until whole duration of sleep is finished.
So to answer your question: in your example exception might or might not be thrown, depending on what part of code is currently executing at the moment you cancel your CancellationTokenSource (I assume that using two cancellation token sources with names cts and tokenSource1 is just a typo in your code, but if it's real code - then such exception cannot be thrown at all, because you cancel cts but Task.Delay waits on tokenSource1).

Categories