How to cancel Task but wait until it finishes? - c#

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.

Related

Task randomly stops executing?

I have a task which I fire off to do some periodic work, although I've noticed it randomly stops executing.
Task.Factory.StartNew(gameProcessor.ProcessAsync, CancellationToken.None,
TaskCreationOptions.LongRunning, TaskScheduler.Default);
I understand I'm not awaiting the task but I have implemented a try catch block for error handling. Even with this I can confirm my breakpoint never hits the catch statement, so I'm confused to what's going on.
Method:
public async Task ProcessAsync()
{
while (true)
{
try
{
await _roomRepository.RunPeriodicCheckAsync();
await Task.Delay(500);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}

CancellationToken is not cancelling all tasks

Newbie to TPL in .NET. Trying to understand CancellationToken and how they are signaled to cancel an executing Task. The below code only transmits one Task being cancelled where as same token is passed to both Task. My assumption is if the timeout happen on first task and it execute ctx.Cancel() I need a little help in understanding why I am only seeing one exception where as both task should be cancelled. What am I missing and how do I ensure both Tasks are cancelled and not taking memory resources.
static void Main(string[] args)
{
Console.WriteLine("Starting application");
var ctx = new CancellationTokenSource();
var token = ctx.Token;
try
{
var task1 = new Program().Run("task1", token);
var task2 = new Program().Run("task2", token);
if (!task1.Wait(1000))
ctx.Cancel();
task2.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine("Aggregate Exception occurred");
foreach (var e in ex.InnerExceptions)
{
Console.WriteLine(e.Message);
}
}
catch (Exception e)
{
Console.WriteLine($"Main Exception: {e.Message}");
}
finally
{
Console.WriteLine("Finish Application");
ctx.Dispose();
}
}
private async Task Run(string name, CancellationToken token)
{
while(true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Task Cancelled");
token.ThrowIfCancellationRequested();
}
Console.WriteLine($"Executing {name} ...");
await Task.Delay(250, token);
}
}
Only one exception is thrown, what happened to other task? Also, Console.WriteLine("Task Cancelled") never got executed.
Output:
Starting application
Executing task1 ...
Executing task2 ...
Executing task2 ...
Executing task1 ...
Executing task1 ...
Executing task2 ...
Executing task2 ...
Executing task1 ...
Aggregate Exception occurred
A task was canceled.
Finish Application
Two things:
You should call ex.Flatten().InnerExceptions take a look at this for reference
The Task.Delay is throwing instead of your cancellation logic. Try wrapping that in a try catch for logging. Alternatively, you could just not pass the token to Task.Delay.

ContinueWith TaskContinuationOptions.OnlyOnFaulted does not seem to catch an exception thrown from a started task

I'm trying to catch an exception thrown from a task method using ContinueWith and OnlyOnFaulted like below. However I get an unhandled exception while I try to run this code.
I'd like the task to run to completion since I have handled the exception already. But Task.Wait() runs into AggregateException.
var taskAction = new Action(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
});
Task t = Task.Factory.StartNew(taskAction);
t.ContinueWith(x => Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
t.Wait();
If I handle the exception in the task method like below, everything will go normal as I expect. Task Runs Into Completion, Exception gets logged and Wait succeeds also.
var taskAction = new Action(() =>
{
try
{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
}
catch (Exception ex)
{
Console.WriteLine("Catching the exception in the Action catch block only");
}
});
Task t = Task.Factory.StartNew(taskAction);
t.ContinueWith(x=> Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
t.Wait();
My question is: Am I using the OnlyOnFaulted correctly or is it always better to handle the exception in the task method itself? I would like the main thread to continue even if task runs into exception. Also, I want to log that exception from task method.
Note: I have to wait for the task method to complete before going further (with or without errors).
To summarize(my understanding so far)
If exception from Task is handled i.e. if wait or await catches the exception then the exception would be propagated to continuedtask onfaulted.
Exception can be caught even in the task method and consumed\handled.
try
{
t.wait();
}
catch(Exception e)
{
LogError(e);
}
In above case before LogError gets called, continued task associated with the main task's onfaulted gets executed.
First of all, you aren't using OnlyOnFaulted correctly. When you use ContinueWith on a task you don't really change that task, you get back a task continuation (which in your case you disregard).
If the original task faulted (i.e. had an exception thrown inside it) it would stay faulted (so calling Wait() on it would always rethrow the exception). The continuation however would run after the task faulted and handle the exception.
That means that in your code you do handle the exception, but you're also rethrowing it with Wait(). The correct code should look like this:
Task originalTask = Task.Run(() => throw new Exception());
Task continuationTask = originalTask.ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
continuationTask.Wait()
// Both tasks completed. No exception rethrown
Now, as Yuval Itzchakov pointed out, you can handle the exception wherever you want but it would better if you were utilizing async-await to wait asynchronously if you can (you can't in Main) instead of blocking with Wait():
try
{
await originalTask;
}
catch (Exception e)
{
// handle exception
}
Am I using the TaskContinutationOptions.OnlyOnFaulted correctly or is it always better to
handle the exception in the task method itself? I would like the main
thread to continue even if task runs into exception.
You can handle exceptions either way, inside or outside. It's a matter of preference and usually depends on the use-case.
Note that one thing you aren't doing is keeping a reference to your continuation. You're using Task.Wait on the original task which propogates the exception regardless of the fact that you have a continuation which handles it.
One thing that bothers me is that you're using Task.Wait which synchronously waits instead of await which asynchronously waits. Thats the reason for the AggregationException. More so, you shouldn't block on asynchronous operations, as that will lead you down a rabit hole you probably don't want to go, with all sorts of synchronization context problems.
What i would personally do, is use await inside of ContinueWith, because its the less verbose option. Also, i'd use Task.Run over Task.Factory.StartNew:
var task = Task.Run(() =>
{
Thread.Sleep(1000);
throw new InvalidOperationException();
}
// Do more stuff here until you want to await the task.
try
{
await task;
}
catch (InvalidOperationException ioe)
{
// Log.
}
The original question happens to run its taskAction on a separate thread. That might not always be the case.
The answer by i3arnon solves the problem well. What if we do not want to use a separate thread? If we want to simply start a task, running it synchronously until we hit IO or a delay, and then continue about our own business. Only at the very end, we will wait for the task to complete.
How do we await the task with rethrowing its exception? Instead of wrapping it in Task.Run(), we will use an empty continuation that will always succeed when the task finishes for any reason.
// Local function that waits a moment before throwing
async Task ThrowInAMoment()
{
await Task.Delay(1000);
Console.WriteLine("Task waited for a sec");
throw new Exception("Throwing for example");
}
// Start the task without waiting for it to complete
var t = ThrowInAMoment();
// We reach this line as soon as ThrowInAMoment() can no longer proceed synchronously
// This is as soon as it hits its "await Delay(1000)"
// Handle exceptions in the task
t.ContinueWith(x => Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
// Continue about our own business
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
// Now we want to wait for the original task to finish
// But we do not care about its exceptions, as they are already being handled
// We can use ContinueWith() to get a task that will be in the completed state regardless of HOW the original task finished (RanToCompletion, Faulted, Canceled)
await t.ContinueWith(task => {});
// Could use .Wait() instead of await if you want to wait synchronously for some reason
It looks like you are on the right track. I have ran your code and get the same results. My suggestion is to use the try/catch directly from inside the action. This will make your code clearer and allow you to log things like which thread it came from, which may be different in the continuation path.
var taskAction = new Action(() =>
{
try{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
}
catch (Exception e)
{
Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ e);
}
});
Task t = Task.Factory.StartNew(taskAction);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
await t;
[Edit] Removed outer exception handler.

Task class - multiple waits and exceptions

usually I encapsulated a task as follows:
Task t = Task.Factory.StartNew(() =>
{
func_can_throw_exception();
}, token).
ContinueWith
((task) =>
{
try
{
task.Wait();
}
catch (AggregateException ae)
{
ae.Handle((x) =>
{
//handle
return true;
});
}
finally
{
}
});
The questions is what happens if I wait (t.Wait();) on this task in a seperate thread (for example the GUI thread). Is this allowed. If there is an exception during task execution, how is this handled?
Don't do it like that. The better way is:
Task.Run(() => {
func_can_throw_exception();
})
.ContinueWith(task => {
do_something_with(task.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
But in the code you provided task.Wait() won't block since ContinueWith only fires after the task is finished.
In the general case, task.Wait() will block the current thread until the task is finished. If the task fails, then Wait will throw an AggregateException. But using Wait can cause deadlocks if you're not careful. It's best to use continuations in TPL code.
It's usually a bad idea to Wait on tasks since it blocks the calling thread as opposed to await which waits asynchronously. It's espically dangerous to block the GUI thread since it can very quickly lead to deadlocks.
You are handling any exception internally so unless Handle throws an exception t.Wait() would not throw any exceptions.
What you should be doing is using async-await:
try
{
await Task.Run(() => func_can_throw_exception());
}
catch (Exception e)
{
// handle exception
}

Handle exception thrown by a task

I have a task running a long time operation in WPF:
Task t = Task.Factory.StartNew(() =>
{
try
{
process(cancelTokenSource.Token, CompressionMethod, OpInfo);
}
catch (OperationCanceledException)
{
logger.Info("Operation cancelled by the user");
}
}, cancelTokenSource.Token);
try
{
t.Wait();
}
catch (AggregateException ae)
{
int i = 0;
}
private void process(CancellationToken token, CompressionLevel level, OperationInfo info)
{
// check hash
if (ComputeHash)
{
logger.Info("HASH CHECKING NOT IMPLEMENTED YET!");
MessageBox.Show(this,"HASH CHECKING NOT IMPLEMENTED YET!", "WARNING", MessageBoxButton.OK, MessageBoxImage.Warning);
}
token.ThrowIfCancellationRequested();
UserMsgPhase = "Operation finished";
return info;
}
Problem is "MessageBox.Show" throws an exception and it is not captured within "catch (AggregateException ae)". I've been reading about TPL exception handling but I don't understand why it is not catched. Please, could you help me?
Once the task is complete you can check its Exception property. You also have Status and IsCompleted properties which may be useful to you...
Check Task.Exception.
If your task is typed (returning a result), then accessing myTask.Result will throw this exception.
Moreover, if you are running .Net 4.5, you could use async/await.
As an example:
public async void MyButton_OnClick(object sender, EventArgs e)
{
try
{
Task t = ...your task...;
var myResult = await t; // do whatever you like with your task's result (if any)
}catch
{
// whatever you need
}
}
as you would do with synchronous code (but this is not an actual synchronous call)
I believe that the question's process method is a Task, so it looks like it could be implement in a different manner:
You can make the process to be implemented as Task and then you will have a task-child within task-parent.
Then you can make use of the TaskCreationOptions.AttachedToParent option.
According to Stephen Toub, using AttachedToParent will help notify children-task exception to the parent-task catch:
any exceptions from faulted children will propagate up to the parent
Task (unless the parent Task observes those exceptions before it
completes).
Example:
I've omitted the cancellation token parts in order for it to be more simple.
Task t = Task.Factory.StartNew(() =>
{
var process = new Task(() =>
{
//Copy here the process logic.
}, TaskCreationOptions.AttachedToParent);
//*Private failure handler*.
process.start();
});
try
{
t.Wait();
}
catch (AggregateException ae)
{
//handle exceptions from process.
}
In addition, you may add a private failure handler like:
//*Private failure handler*.
var failHandler = child.ContinueWith(t =>
{
//Oops, something went wrong...
}, TaskContinuationOptions.AttachedToParent|TaskContinuationOptions.OnlyOnFaulted);

Categories