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

14,086

Solution 1

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
}

Solution 2

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.
}
Share:
14,086
seesharpconcepts
Author by

seesharpconcepts

Updated on June 05, 2022

Comments

  • seesharpconcepts
    seesharpconcepts almost 2 years

    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.

  • Pranay Rana
    Pranay Rana over 9 years
    there is no need of inner try catch ....to catch exception you need to put try catch where you are calling wait or result on task...
  • Phillip Scott Givens
    Phillip Scott Givens over 9 years
    Agreed, but I took the time to read the specification "I want to log that exception from task method."
  • seesharpconcepts
    seesharpconcepts over 9 years
    @PhillipScottGivens I was thinking why ContiueWith task onfaulted is not getting executed even on the exception in the task method. I read through a MSDN article which suggests using ContinueWith and onlyonfaulted to handle exceptions from the tasks.
  • Phillip Scott Givens
    Phillip Scott Givens over 9 years
    When I ran your code, the ContinueWith task did indeed get executed, but it did not stop the exception from bubbling up further. Try your code again and put a breakpoint on your Console.Writeline. Consider moving it to a different line for clarity.
  • seesharpconcepts
    seesharpconcepts over 9 years
    thanks for your answer, esp exception propogation. I'm unable to upvote your answer(may be because i'm new here and have not enough reputation :( )
  • seesharpconcepts
    seesharpconcepts over 9 years
    This is the information I was looking for. I'm unable to upvote may be because i'm new here and have not enough reputation :(
  • seesharpconcepts
    seesharpconcepts over 9 years
    I need synchronous wait here in my case. intention being there are two methods which are to be completed(in parallel) before I go further so i guess await would not work in my scenario. I can even go with Parallel.Invoke for the two methods.
  • seesharpconcepts
    seesharpconcepts over 9 years
    @PhillipScottGivens : as I3arnon mentioned in his answer, continuetask will get executed once the exception from main task is handled. Note: I did not downvote your answer, thanks for helping me out.
  • i3arnon
    i3arnon over 9 years
    @seesharpconcepts And yes, you can't upvote an answer until you reach 15 points. More on that here: meta.stackexchange.com/q/1661/246036
  • Yuval Itzchakov
    Yuval Itzchakov over 9 years
    Why do you have to synchronously wait?
  • seesharpconcepts
    seesharpconcepts over 9 years
    Let say, I have to call methodA() which returns a list and methodB() which returns another list. I will have to call another service method with input parameter as the list which is some kind of union of lists returned by methodA() and methodB(). In this case, I can parallely invoke methodA() and methodB() but I will have to wait for both of them to return and form the resultant list. This list I will have send to the service and I have form another doc with what service returns. This is an user interaction and user will wait for the action to be over.
  • Yuval Itzchakov
    Yuval Itzchakov over 9 years
    @seesharpconcepts Why not simply use await Task.WhenAll(taskA, taskB);?
  • C.M.
    C.M. almost 6 years
    With this code a a task cancellation exception is thrown if you await the continuation task and there is no exception in the first task.
  • C.M.
    C.M. almost 6 years
    I actually prefer this way (handle exceptions within the task and use return values to signal success/failure) when running tasks concurrently over using task continuations with OnlyOnFaulted. The control flow gets really messy when you have many tasks and you need to know which task/continuations to await.