Task is cancelled only when ThrowIfCancellationRequested is called - c#

I have a task that is doing an infinite loop.
I have a CancellationToken that I pass both to the Task.Run call as to the actual ExecutePoll function.
I wait for a few seconds and Cancel the token.
There is one Continuation that should run when the task is cancelled.
Turns out this continuation only runs if I explicitly call cancellationToken.ThrowIfCancellationRequested();. If I only break out of the loop the task is always in the status: RanToCompletion
Can anyone share some light as to what I am getting wrong here?
CODE:
using System.Threading;
using System.Threading.Tasks;
namespace TaskCancellationTest
{
class Program
{
private static CancellationTokenSource _pollProcessTokenSource;
static async Task Main(string[] args)
{
InitPollingProcess();
await Task.Delay(5000);
Console.WriteLine("Cancelling loop");
_pollProcessTokenSource.Cancel();
Console.WriteLine("Loop cancelled");
Console.ReadLine();
}
private static void InitPollingProcess()
{
try
{
_pollProcessTokenSource = new CancellationTokenSource();
Task pollForListenerConfigs = Task.Run(async () =>
{
await ExecutePoll(_pollProcessTokenSource.Token);
},
_pollProcessTokenSource.Token);
pollForListenerConfigs.ContinueWith(_ =>
{
Console.WriteLine("Poll process stopped!");
}, TaskContinuationOptions.OnlyOnCanceled);
pollForListenerConfigs.ContinueWith(t =>
{
Console.WriteLine($"Task status: {t.Status}");
});
}
catch (Exception ex)
{
Console.WriteLine($"Poll process failed with Exception:\n {ex.Message}");
}
}
private static async Task ExecutePoll(CancellationToken cancellationToken)
{
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Exit from poll loop!");
//cancellationToken.ThrowIfCancellationRequested(); // UNCOMMENT TO MAKE CONTINUATION RUN
break;
}
Console.WriteLine("Looping...");
await Task.Delay(1000);
}
}
}
}

There is nothing wrong, it is well explained here:
You can terminate the operation by using one of these options:
By simply returning from the delegate. In many scenarios this is sufficient; however, a task instance that is canceled in this way
transitions to the TaskStatus.RanToCompletion state, not to the
TaskStatus.Canceled state.
By throwing a OperationCanceledException and passing it the token on which cancellation was requested. The preferred way to do this is
to use the ThrowIfCancellationRequested method. A task that is
canceled in this way transitions to the Canceled state, which the
calling code can use to verify that the task responded to its
cancellation request.

Related

Cancellation Token doesen't work in Parallel.Foreach in c# [duplicate]

This question already has answers here:
How to cancel a Task in await?
(4 answers)
Using CancellationToken for timeout in Task.Run does not work [duplicate]
(4 answers)
Closed 1 year ago.
I have a problem with cancellation token that doesn't work. I created a simple code example of my actual project. This is my scenario:
class Program
{
static void Main(string[] args)
{
var taskCheker = new TaskChecker();
taskCheker.Test();
Console.ReadKey();
}
}
public class TaskChecker
{
public void Test()
{
var list = Enumerable.Range(0, 10);
Parallel.ForEach(list, (item) =>
{
CancellationTokenSource cancellationToken = new CancellationTokenSource();
cancellationToken.CancelAfter(1000);
try
{
var task = Task.Run(() =>
{
Console.WriteLine($"item: {item} with thread: {Thread.CurrentThread.ManagedThreadId} started");
LongTask();
Console.WriteLine($"item: {item} with thread: {Thread.CurrentThread.ManagedThreadId} finished");
}, cancellationToken.Token);
Task.WaitAll(new[] { task });
}
catch (TaskCanceledException)
{
Console.WriteLine("TaskCanceledException runned");
}
catch (Exception)
{
Console.WriteLine("Exception runned");
}
});
Console.WriteLine("Parallel finished");
Console.ReadKey();
}
public void LongTask()
{
Thread.Sleep(2000);
}
}
I have a Parallel.Foreach that calls inner Task for each item. Inner Task has a cancellation token with 1 second. Inner Task call a LongTask method that it takes 2 seconds, and I expect that all of my inner tasks throw timeout. But all of them call and after 2 seconds will complete. So I don't know why my cancellation token doesn't work.
Thank you so much to your help
In Task.Run if the IsCancellationRequested is set to true before the task is actually running then it won't run.
But if the task already running before the IsCancellationRequested is true then it's your responsibility to call ThrowIfCancellationRequested in your task.
This is from MSDN:
Cancellation is cooperative and is not forced on the listener. The listener determines how to gracefully terminate in response to a cancellation request.
MSDN Source

Is there a way I can cause a running method to stop immediately with a cts.Cancel();

I have code that creates a CancellationTokenSource and that passes it to a method.
I have code in another are of the app that issues a cts.Cancel();
Is there a way that I can cause that method to stop immediately without me having to wait for the two lines inside the while loop to finish?
Note that I would be okay if it caused an exception that I could handle.
public async Task OnAppearing()
{
cts = new CancellationTokenSource();
await GetCards(cts.Token);
}
public async Task GetCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts);
await CheckAvailability();
}
}
What I can suggest:
Modify GetViewablePhrases and CheckAvailability so you could pass the CancellationToken to them;
Use ct.ThrowIfCancellationRequested() inside these functions;
Try / Catch the OperationCanceledException inside GetCards;
As for your your functions I don't know how exactly they work inside. But let's assume you have a long running iteration inside one of them:
CheckAvailability(CancellationToken ct)
{
for(;;)
{
// if cts.Cancel() was executed - this method throws the OperationCanceledException
// if it wasn't the method does nothing
ct.ThrowIfCancellationRequested();
...calculations...
}
}
Or let's say you are going to access your database inside one of the function and you know that this process is going to take a while:
CheckAvailability(CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
AccessingDatabase();
}
This will not only prevent your functions from proceeding with execution, this also will set the executioner Task status as TaskStatus.Canceled
And don't forget to catch the Exception:
public async Task GetCards(CancellationToken ct)
{
try
{
App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts, ct);
await CheckAvailability(ct);
}
catch(OperationCanceledException ex)
{
// handle the cancelation...
}
catch
{
// handle the unexpected exception
}
}
If you are OK with cancelling not the task, but the awaiting of the task, you could use a cancelable wrapper. In case of cancellation the underlying task will continue running, but the wrapper will complete immediately as canceled.
public static Task AsCancelable(this Task task,
CancellationToken cancellationToken)
{
var cancelable = new Task(() => { }, cancellationToken);
return Task.WhenAny(task, cancelable).Unwrap();
}
public static Task<T> AsCancelable<T>(this Task<T> task,
CancellationToken cancellationToken)
{
var cancelable = new Task<T>(() => default, cancellationToken);
return Task.WhenAny(task, cancelable).Unwrap();
}
Usage example:
await GetCards(cts.Token).AsCancelable(cts.Token);
This extension method can also be implemented using a TaskCompletionSource<T> (instead of the Task<T> constructor).

How Do I Create a Looping Service inside an C# Async/Await application?

I have written a class with a method that runs as a long-running Task in the thread pool. The method is a monitoring service to periodically make a REST request to check on the status of another system. It's just a while() loop with a try()catch() inside so that it can handle its own exceptions and and gracefully continuing if something unexpected happens.
Here's an example:
public void LaunchMonitorThread()
{
Task.Run(() =>
{
while (true)
{
try
{
//Check system status
Thread.Sleep(5000);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
});
}
It works fine, but I want to know if there's another pattern I could use that would allow the Monitor method to run as regular part of a standard Async/Await application, instead of launching it with Task.Run() -- basically I'm trying to avoid fire-and-forget pattern.
So I tried refactoring the code to this:
public async Task LaunchMonitorThread()
{
while (true)
{
try
{
//Check system status
//Use task.delay instead of thread.sleep:
await Task.Delay(5000);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
}
But when I try to call the method in another async method, I get the fun compiler warning:
"Because this call is not awaited, execution of the current method continues before the call is completed."
Now I think this is correct and what I want. But I have doubts because I'm new to async/await. Is this code going to run the way I expect or is it going to DEADLOCK or do something else fatal?
What you are really looking for is the use of a Timer. Use the one in the System.Threading namespace. There is no need to use Task or any other variation thereof (for the code sample you have shown).
private System.Threading.Timer timer;
void StartTimer()
{
timer = new System.Threading.Timer(TimerExecution, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}
void TimerExecution(object state)
{
try
{
//Check system status
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
From the documentation
Provides a mechanism for executing a method on a thread pool thread at specified intervals
You could also use System.Timers.Timer but you might not need it. For a comparison between the 2 Timers see also System.Timers.Timer vs System.Threading.Timer.
If you need fire-and-forget operation, it is fine. I'd suggest to improve it with CancellationToken
public async Task LaunchMonitorThread(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
//Check system status
//Use task.delay instead of thread.sleep:
await Task.Delay(5000, token);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
}
besides that, you can use it like
var cancellationToken = new CancellationToken();
var monitorTask = LaunchMonitorThread(cancellationToken);
and save task and/or cancellationToken to interrupt monitor wherever you want
The method Task.Run that you use to fire is perfect to start long-running async functions from a non-async method.
You are right: the forget part is not correct. If for instance your process is going to close, it would be neater if you kindly asked the started thread to finish its task.
The proper way to do this would be to use a CancellationTokenSource. If you order the CancellationTokenSource to Cancel, then all procedures that were started using Tokens from this CancellationTokenSource will stop neatly within reasonable time.
So let's create a class LongRunningTask, that will create a long running Task upon construction and Cancel this task using the CancellationTokenSource upon Dispose().
As both the CancellationTokenSource as the Task implement IDisposable the neat way would be to Dispose these two when the LongRunningTask object is disposed
class LongRunningTask : IDisposable
{
public LongRunningTask(Action<CancellationToken> action)
{ // Starts a Task that will perform the action
this.cancellationTokenSource = new CancellationTokenSource();
this.longRunningTask = Task.Run( () => action (this.cancellationTokenSource.Token));
}
private readonly CancellationTokenSource cancellationTokenSource;
private readonly Task longRunningTask;
private bool isDisposed = false;
public async Task CancelAsync()
{ // cancel the task and wait until the task is completed:
if (this.isDisposed) throw new ObjectDisposedException();
this.cancellationTokenSource.Cancel();
await this.longRunningTask;
}
// for completeness a non-async version:
public void Cancel()
{ // cancel the task and wait until the task is completed:
if (this.isDisposed) throw new ObjectDisposedException();
this.cancellationTokenSource.Cancel();
this.longRunningTask.Wait;
}
}
Add a standard Dispose Pattern
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing && !this.isDisposed)
{ // cancel the task, and wait until task completed:
this.Cancel();
this.IsDisposed = true;
}
}
Usage:
var longRunningTask = new LongRunningTask( (token) => MyFunction(token)
...
// when application closes:
await longRunningTask.CancelAsync(); // not necessary but the neat way to do
longRunningTask.Dispose();
The Action {...} has a CancellationToken as input parameter, your function should regularly check it
async Task MyFunction(CancellationToken token)
{
while (!token.IsCancellationrequested)
{
// do what you have to do, make sure to regularly (every second?) check the token
// when calling other tasks: pass the token
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
}
Instead of checking for Token, you could call token.ThrowIfCancellationRequested. This will throw an exception that you'll have to catch

How to cancel Task but wait until it finishes?

I have threaded task wich performs some operation in loop:
static void TaskAction(CancellationToken ct)
{
while (SomeCondition())
{
DoSomeSeriousJob();
ct.ThrowIfCancellationRequested();
}
}
static void DoSomeSeriousJob()
{
Console.WriteLine("Serious job started");
Thread.Sleep(5000);
Console.WriteLine("Serious job done");
}
I start it and then cancel after some period of time:
var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => TaskAction(cts.Token), cts.Token);
Thread.Sleep(1000);
cts.Cancel();
This operation must be finished correctly, I don't want to interrupt it. But I want to send a cancellation request to my task and wait until it finishes correctly (by which I mean it gets to some point in code).
I tryed following approaches:
1. Wait(CancellationToken ct)
try
{
task.Wait(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Task cancelled");
}
// Must be joined here.
In this case program returns immediately from Wait(). The task continues to run until ThrowIfCancellationRequested() but if main thread exits the task gets interrupted too.
2. Wait()
try
{
task.Wait();
}
catch (OperationCanceledException)
{
Console.WriteLine("Task cancelled");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Here main thread waits for completion but at the end AggregateException is risen with InnerException = TaskCancelledException (not OperationCancelledException).
3. Check IsCancellationRequested() and no exceptions
static void TaskAction(CancellationToken ct)
{
while (SomeCondition())
{
DoSomeSeriousJob();
if (ct.IsCancellationRequested)
break;
}
}
// ...
task.Wait();
In this case no exceptions are risen but the task gets status RanToCompletion in the end. This is not distiguishable from correct completion when SomeCodition() starts to return false.
All these problem have easy workarounds but I wonder, may be I'm missing something? Could anybody advise me better solution?
If you want to wait for the task to complete (or gets cancelled) synchronously, you can try this:
cts.Cancel();
Task.Run(async () => {
try {
await task;
}
catch (OperationCanceledException ex) {
// ...
}
).Wait();
So that you can directly catch OperationCanceledException instead of catching an AggregateException.
Edit:
Wait(CanecllationToken)
This approach won't work for that purpose.
MSDN statement:
Waits for the Task to complete execution. The wait terminates if a cancellation token is canceled before the task completes.
Wait()
You can use this approach but as you can see, you should expect an AggregateException not OperationCanceledException. It is also specified in documents of the method.
The AggregateException.InnerExceptions collection contains a TaskCanceledException object.
So in this approach, in order to make sure operation is cancelled, you can check if inner expection contains a TaskCanceledException or not.
Check IsCancellationRequested() and no exceptions
In this way, this is obvious that no exception is thrown and you can't find out if the operation is cancelled or not.
If you don't want to wait synchronously, everything works as expected:
cts.Cancel();
try {
await task;
}
catch (OperationCanceledException ex) {
// ...
}
Try this:
try
{
task.GetAwaiter().GetResult();
}
catch (OperationCanceledException)
{
Console.WriteLine("Task cancelled");
}
You'll get an OperationCanceledException and it won't be wrapped with AggregateException.

Using CancellationToken for timeout in Task.Run does not work [duplicate]

This question already has answers here:
How to cancel a Task in await?
(4 answers)
Closed 3 years ago.
OK, my questions is really simple. Why this code does not throw TaskCancelledException?
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationTokenSource(500).Token).Result;
Console.WriteLine(v); // this outputs 10 - instead of throwing error.
Console.Read();
}
But this one works
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationToken(true).Token).Result;
Console.WriteLine(v); // this one throws
Console.Read();
}
Cancellation in Managed Threads:
Cancellation is cooperative and is not forced on the listener. The listener determines how to gracefully terminate in response to a cancellation request.
You didn't write any code inside your Task.Run method to access your CancellationToken and to implement cancellation - so you effectively ignored the request for cancellation and ran to completion.
There is a difference in canceling a running task, and a task scheduled to run.
After the call to the Task.Run method, the task is only scheduled, and probably have not been executed yet.
When you use the Task.Run(..., CancellationToken) family of overloads with cancellation support, the cancellation token is checked when the task is about to run. If the cancellation token has IsCancellationRequested set to true at this time, an exception of the type TaskCanceledException is thrown.
If the task is already running, it is the task's responsibility to call the ThrowIfCancellationRequested method, or just throw the OperationCanceledException.
According to MSDN, it's just a convenience method for the following:
if (token.IsCancellationRequested)
throw new OperationCanceledException(token);
Note the different kind of exception used in this two cases:
catch (TaskCanceledException ex)
{
// Task was canceled before running.
}
catch (OperationCanceledException ex)
{
// Task was canceled while running.
}
Also note that TaskCanceledException derives from OperationCanceledException, so you can just have one catch clause for the OperationCanceledException type:
catch (OperationCanceledException ex)
{
if (ex is TaskCanceledException)
// Task was canceled before running.
// Task was canceled while running.
}
In the first variant of your code, you're not doing anything to manage the cancellation token.
For example, you're not checking whether token.IsCancellationRequested returns true (and then throwing an exception) or calling ThrowIfCancellationRequested() from your CancellationToken object.
Also, the Task.Run overload you used checks whether the token is already canceled or not when the task is about to start and your code states that token will report cancellation after 500 ms.
So, your code is just ignoring the cancellation request and that's why the task ran to completion.
You should do something like this:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
return 10;
}, ct).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
or this, without passing the token, as others already noted:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
ct.ThrowIfCancellationRequested();
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
The second variant of your code works, because you're already initializing a token with a Canceled state set to true. Indeed, as stated here:
If canceled is true, both CanBeCanceled and IsCancellationRequested will be true
the cancellation has already been requested and then the exception TaskCanceledException will be immediately thrown, without actually starting the task.
Another implementation using Task.Delay with token instead it Thread.Sleep.
static void Main(string[] args)
{
var task = GetValueWithTimeout(1000);
Console.WriteLine(task.Result);
Console.ReadLine();
}
static async Task<int> GetValueWithTimeout(int milliseconds)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(milliseconds);
token.ThrowIfCancellationRequested();
var workerTask = Task.Run(async () =>
{
await Task.Delay(3500, token);
return 10;
}, token);
try
{
return await workerTask;
}
catch (OperationCanceledException )
{
return 0;
}
}

Categories