How to manage properly an exception in a Task with ContinueWith - c#

After reading information about task and exepcion management, I am using this code to manage an exception thrown in a Task:
Task<Object> myTask = Task.Factory.StartNew<Object>(doTask, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
myTask .ContinueWith(task => afterTask(task), TaskScheduler.FromCurrentSynchronizationContext());
Where doTask and AfterTask are:
private <Object> doTask() {
throw new Exception("BOOM");
}
private afterTask(Task<Object> aTask) {
if (aTask.IsFaulted)
{
MessageBox.Show(aTask.Exception.InnerException.Message);
}
else //whatever
}
When Exception Boom is thrown the Visual Studio shows an alert informing that an exception has not been caught but if I continue executing the exception is processed in the afterTask function.
Is this code correct or I missunderstood some basic behaviour of the task? There is any way to avoid the alert from the debugger that the execption has not been caught? Is a bit annoying...
Thanks in advance

Try this instead:
task.ContinueWith(
t =>
t.Exception.Handle(ex =>
{
logger.Error(ex.Message, ex);
return false;
})
, TaskContinuationOptions.OnlyOnFaulted
);
By using the TaskContinuationOptions.OnlyOnFaulted, you run your ContinueWith block only if an exception is thrown by the original task.
Aditionally, you can choose whether to return true or false from the lambda passed to Handle, indicating whether the exception has been handled or not. In my case, I didn't want to stop the exception from propagating. You might want to change it to return true in your case.

try
{
var t1 = Task.Delay(1000);
var t2 = t1.ContinueWith(t =>
{
Console.WriteLine("task 2");
throw new Exception("task 2 error");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
var t3 = t2.ContinueWith(_ =>
{
Console.WriteLine("task 3");
return Task.Delay(1000);
}, TaskContinuationOptions.OnlyOnRanToCompletion).Unwrap();
// The key is to await for ALL tasks rather than just
// the first or last task.
await Task.WhenAll(t1, t2, t3);
}
catch (AggregateException aex)
{
aex.Flatten().Handle(ex =>
{
// handle your exceptions here
Console.WriteLine(ex.Message);
return true;
});
}

Related

C# Throw OperationCanceledException from inside CancellationToken.Register

I have a long running operation that I want to cancel after, say 5 secs. Unfortunately, polling for IsCancellationRequested is not possible (long story).
I used the code below to throw an OperationCanceledException inside the cancellation callback. I wanted to catch the exception in the main thread and handle it so that I can exit the application completely.
This doesn't seem to work properly as this results in an unhandled exception and the application doesn't terminate gracefully.
Any help is appreciated. Thanks!
void TestTimeOut()
{
var cts = new CancellationTokenSource();
cts.CancelAfter(5000);
try
{
var task = Task.Run(() => LongRunningOperation(cts.Token));
task.ContinueWith(t => Console.WriteLine("Operation cancelled"), TaskContinuationOptions.OnlyOnFaulted);
task.Wait();
}
catch (AggregateException e)
{
//Handle
}
}
void LongRunningOperation(CancellationToken token)
{
CancellationTokenRegistration registration = token.Register(
() =>
{
throw new OperationCanceledException(token);
});
using (registration)
{
// long running operation here
}
}
Your code has many No-No-es, but I guess you just using it as a demo for your problem.
The Solution is TaskCompletionSource, My Demo is ugly too, too many layers of Task.Run(), if you use Async, you should async all the way down. So don't use it in PRD, study TaskCompletionSource yourself and figure out a better solution.
static void LongRunningOperation(CancellationToken token)
{
TaskCompletionSource<int> tcs1 = new TaskCompletionSource<int>();
token.Register(() => { tcs1.TrySetCanceled(token); });
Task.Run(() =>
{
// long running operation here
Thread.Sleep(10000);
tcs1.TrySetResult(0);
}, token);
tcs1.Task.Wait();
}
You are catching an AggregateException but actuall throwing an OperationCanceledException which will not be caught.
Change to catch all types of exceptions such as
catch (Exception ex) { ... }
to resolve.

How to capture AggregateException? [duplicate]

Just noticed strange thing: to catch exception in caller from new Task, lambda MUST be marked as async!? Is it really necessary even if delegate has no await operators at all?
try
{
//Task.Run(() => // exception is not caught!
Task.Run(async () => // unnecessary async!?!
{
throw new Exception("Exception in Task");
}).Wait();
}
catch (Exception ex)
{
res = ex.Message;
}
Why there is neccesary for async operator?
All documentation i can find tells that delegate must not return Void and Task must be awaited for exception to propogate up to caller.
Added full code:
class Program
{
static void Main(string[] args)
{
var p = new Program();
p.Run();
}
public void Run()
{
string result;
try
{
result = OnSomeEvent((s, ea) => RunSomeTask());
}
catch (Exception ex) // Try to catch unhandled exceptions here!
{
result = ex.Message;
}
Console.WriteLine(result);
Console.ReadKey();
}
// Some other Framework bult-in event (can not change signature)
public string OnSomeEvent(EventHandler e)
{
e.Invoke(null, new EventArgs());
return "OK";
}
private async Task RunSomeTask()
{
await Task.Run(async () => // do not need async here!!!
//await Task.Run(() => // caller do not catches exceptions (but must)
{
throw new Exception("Exception in Task1");
});
}
}
So the qestion is how to catche ex. without asyn keyword???
Methods that return Task - such as Task.Run or async methods - will place any exceptions on that returned Task. It's up to you to observe that exception somehow. Normally this is done with await, like this:
await Task.Run(() => { throw ... });
In your case, the problem is in this line:
result = OnSomeEvent((s, ea) => RunSomeTask());
In this code, RunSomeTask is returning a Task, and that Task is never awaited. In order to observe the exception, you should await that task.
When using async/await, exceptions are automatically unwrapped at the site of the await. When using a Task and .Wait(), any exception are wrapped when they come out of the Task, and thus getting information requires you to dig into the Task.Exception property, since they do not propagate up the call stack.
See https://dotnetfiddle.net/MmEXsT

How can a ContinueWith task catch exception of C# task that returns void?

How can a ContinueWith task catch the exception of a C# async task that returns void? (We're using VS 2010, so no async/await keywords yet). Our standard pattern when the task returns something is:
Task<int> task = ...;
task.ContinueWith(t =>
{
try
{
int result = task.Result; //This will throw if there was an error.
//Otherwise keep processing
}
catch(Exception e)
{ ... }
}, TaskScheduler.FromCurrentSynchronizationContext());
But if the task doesn't return anything, there is no "task.Result". So what's the best way to handle that task that doesn't return anything?
EDIT: Here's what I want to accomplish:
Task taskWithNoReturnType = ...
taskWithNoReturnType.ContinueWith( t =>
{
try
{
//HOW CAN I KNOW IF THERE WAS AN EXCEPTION ON THAT TASK???
//Otherwise, keep processing this callback
}
catch(Exception e)
{ ... }
}, TaskScheduler.FromCurrentSynchronizationContext());
Task.Exception gets the AggregateException that caused the Task to end prematurely. If the Task completed successfully or has not yet thrown any exceptions, this will return null.
Example:
Task.Factory
.StartNew(
() => { DoSomething(); /* throws an exception */ } )
.ContinueWith(
p =>
{
if (p.Exception != null)
p.Exception.Handle(x =>
{
Console.WriteLine(x.Message);
return true;
});
});
Found the answer --
Task taskWithNoReturnType = ...
taskWithNoReturnType.ContinueWith( t =>
{
try
{
t.Wait(); //This is the key. The task has already completed (we
//are in the .ContinueWith() after all) so this won't really wait,
//but it will force any pending exceptions to propogate up and
//then the catch will work normally.
//Else, if we get here, then there was no exception.
//<process code normally here>
}
catch(Exception e)
{ ... }
}, TaskScheduler.FromCurrentSynchronizationContext());

Mark task exception as handled

How can I mark an exception thrown in a task as handled. The problem is when I call the Wait() method of a task, it will throw an AggregateException even when I have already handled the AggregateException a long time ago.
The following code snippet shows the problem I want to solve. I my original code I handle the AggregateException in one part of my code and I call the Wait() method in another part of my code. But the problem is the same.
static void Main(string[] args)
{
Task task = null;
try
{
task = new Task(() =>
{
Console.WriteLine("Task started");
Thread.Sleep(1000);
throw new InvalidOperationException("my test exception");
});
task.ContinueWith(t =>
{
Console.WriteLine("Task faulted");
AggregateException ae = t.Exception;
ae.Flatten().Handle(ex =>
{
if (typeof(InvalidOperationException) == ex.GetType())
{
Console.WriteLine("InvalidOperationException handled --> " + ex.Message);
return true;
}
return false;
});
}, TaskContinuationOptions.OnlyOnFaulted);
task.Start();
Thread.Sleep(2000);
task.Wait();
}
catch (AggregateException ae)
{
Console.WriteLine("AggregateException thrown again!!! Why???");
ae.Flatten().Handle(ex =>
{
Console.WriteLine(ex.Message);
return true;
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("Finished");
Console.Read();
}
The code above produces the following output:
Task started
Task faulted
InvalidOperationException handled --> my test exception
AggregateException thrown again!!! Why???
my test exception
Finished
When a faulted task is Waited the exception is rethrown. It would be unreliable design if an exception would be thrown just sometimes.
But, if you're adding a continuation that handles the exception and you don't want it thrown again then simply don't Wait that task again. Wait the continuation task (that you're currently not using) instead. It would only complete after the original task completed and if you need the result simply have the continuation return that. This way the exception would be handled only once:
Task continuation = task.ContinueWith(t =>
{
Console.WriteLine("Task faulted");
AggregateException ae = t.Exception;
ae.Flatten().Handle(ex =>
{
if (typeof(InvalidOperationException) == ex.GetType())
{
Console.WriteLine("InvalidOperationException handled --> " + ex.Message);
return true;
}
return false;
});
}, TaskContinuationOptions.OnlyOnFaulted);
task.Start();
Thread.Sleep(2000);
continuation.Wait();
Note: That will throw a TaskCanceledException when the original task doesn't throw an exception because the continuation is canceled (due to TaskContinuationOptions.OnlyOnFaulted). To avoid that simply remove the flag and check whether t.IsFaulted.

How to make Task.WaitAll() to break if any exception happened?

I want to make Task.WaitAll() to break out if any of the running tasks throws an exception, so that I don't have to wait for 60 seconds to finish. How do I achieve such behavior? If WaitAll() cannot achieve that, is there any other c# feature or workaround?
Task task1 = Task.Run(() => throw new InvalidOperationException());
Task task2 = ...
...
try
{
Task.WaitAll(new Task[]{task1, task2, ...}, TimeSpan.FromSeconds(60));
}
catch (AggregateException)
{
// If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds.
}
The following should do it without altering the code of the original tasks (untested):
static bool WaitAll(Task[] tasks, int timeout, CancellationToken token)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
var proxyTasks = tasks.Select(task =>
task.ContinueWith(t => {
if (t.IsFaulted) cts.Cancel();
return t;
},
cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current).Unwrap());
return Task.WaitAll(proxyTasks.ToArray(), timeout, cts.Token);
}
Note it only tracks faulted tasks (those which threw). If you need to track cancelled tasks as well, make this change:
if (t.IsFaulted || t.IsCancelled) cts.Cancel();
Updated, waiting on the task proxies is redundant here, as pointed out by #svick in the comments. He proposes an improved version: https://gist.github.com/svick/9992598.
One way of doing that is to use CancellationTokenSource. You create cancellationtokensource, and pass it as an argument to Task.WaitAll. The idea is to wrap your task in try/catch block, and in case of exception, call cancel on cancellationtokensource.
Here's sample code
CancellationTokenSource mainCancellationTokenSource = new CancellationTokenSource();
Task task1 = new Task(() =>
{
try
{
throw new Exception("Exception message");
}
catch (Exception ex)
{
mainCancellationTokenSource.Cancel();
}
}, mainCancellationTokenSource.Token);
Task task2 = new Task(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(3));
Console.WriteLine("Task is running");
}, mainCancellationTokenSource.Token);
task1.Start();
task2.Start();
Task.WaitAll(new[] { task1, task2},
6000, // 6 seconds
mainCancellationTokenSource.Token
);
}
catch (Exception ex)
{
// If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds.
}
Parallel class can do the job for you. You can use Parallel.For, ForEach or Invoke.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Sample_04_04_2014_01
{
class Program
{
public static void Main(string[] args)
{
try
{
Parallel.For(0,20, i => {
Console.WriteLine(i);
if(i == 5)
throw new InvalidOperationException();
Thread.Sleep(100);
});
}
catch(AggregateException){}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}
If one of these tasks throws an exception then no other task will be executed excepting those whose execution was already started. For, ForEach and Invoke are waiting for all tasks to complete before to resume control to the calling code. You can have even a finer grain control if you use ParallelLoopState.IsExceptional. Parallel.Invoke is more suited for your case.
I wanted to suggest a slight modification to Noseratio's excellent answer above. In my case I needed to preserve the original exception thrown, and in a surrounding try/catch distinguish between cancelled and exception states.
public static void WaitUnlessFault( Task[] tasks, CancellationToken token )
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
foreach ( var task in tasks ) {
task.ContinueWith(t =>
{
if ( t.IsFaulted ) cts.Cancel();
},
cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current);
}
try {
Task.WaitAll(tasks, cts.Token);
}
catch ( OperationCanceledException ex ) {
var faultedTaskEx = tasks.Where(t => t.IsFaulted)
.Select(t => t.Exception)
.FirstOrDefault();
if ( faultedTaskEx != null )
throw faultedTaskEx;
else
throw;
}
}

Categories

Resources