8

I have this simple code :

var g=  Task.Factory.StartNew<int> (() => 8)
       .ContinueWith (ant =>{throw null;})
       .ContinueWith (a =>{ Console.WriteLine("OK");},TaskContinuationOptions.NotOnFaulted);

 try{
      Console.WriteLine("1");
      g.Wait();
      Console.WriteLine("2");
     }

catch (AggregateException  ex)
      {Console.WriteLine("catch"); }

The Output :

1
catch
System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.

msdn :

TaskContinuationOptions.NotOnFaulted

Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled exception. This option is not valid for multi-task continuations.

ok .

enter image description here

And it is ok - not showing this line cause the prev line DID throw exception.

Questions :

  • Do I get the AggregateException exception because I haven't inspected the Exception property ?

  • Must I always inspect if the antecedent throw an exception ( in each line ? ) ? ( I can't check each line ! it doesn't make any sense and very annoying)

  • Wasn't the try catch block should have swallow the exception ? ( I thought that all exceptions bubble up to the wait method....so ? )

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • Alas, this topic is not so rare. I coped with it by using several extension methods in my custom `TaskEx` class. – Sergey Teplyakov Dec 06 '12 at 09:49
  • BTW you can take a look at great article by Joe Albahari on this topic: http://www.albahari.com/threading/part5.aspx There is a good description for such kind of issues. – Sergey Teplyakov Dec 06 '12 at 09:51
  • I mean *unfortunately* this is not so rare topic. – Sergey Teplyakov Dec 06 '12 at 09:52
  • I don't know, because if you take this example from there, Joe gave you solution as well and clearly described this behavior. – Sergey Teplyakov Dec 06 '12 at 09:54
  • @SergeyTeplyakov NO he doesn't. He talkes about inspecting the Exception property ( which if you don't- you will get the exception) but he DOESN'T talk about inspecting exception in fluent continues way. he created task1 and task2. which is not like my sample. – Royi Namir Dec 06 '12 at 09:56
  • look at the link http://www.albahari.com/threading/part5.aspx#_Continuations_and_exceptions – Royi Namir Dec 06 '12 at 09:57
  • Yes, and later in this section you can find following: '“Canceled” means one of two things: The antecedent was canceled via its cancellation token. In other words, an OperationCanceledException was thrown on the antecedent — whose CancellationToken property matched that passed to the antecedent when it was started. **The antecedent was implicitly canceled because it didn’t satisfy a conditional continuation predicate**.' – Sergey Teplyakov Dec 06 '12 at 10:00
  • @SergeyTeplyakov **Cancel token are totaly unrelated here.** ( to my specific questions) They throw `OperationCanceledException` while myne is cached with `AggregateException` – Royi Namir Dec 06 '12 at 10:03
  • Yes, I bolded relevant point, but pasted whole section. – Sergey Teplyakov Dec 06 '12 at 10:04
  • I got you. Give me a second to explain! – Sergey Teplyakov Dec 06 '12 at 10:15
  • @SergeyTeplyakov ok thanks ( please also look at my comment to your answer below) – Royi Namir Dec 06 '12 at 10:15

3 Answers3

5

Do I get the AggregateException exception because I haven't inspected the Exception property ?

No, you get an exception, because task g cancels by TPL(because, as msdn stated, this task will not scheduled if antescendent task throws an exception).

We have 3 tasks here:

  1. Original Task (that uses StartNew)
  2. First Continuation Task (that throws an exception)
  3. Second Continuation Task (that prints OK) (this is g task from your code).

The issue is that you ask TPL to start 3d task only if 2nd task will finished successfully. This means that if this condition will not met TPL will cancel your newly created task entirely.

You got unobserved task exception because you have temporary task (task 2 in my list) that you never observe. An because you never observe it faulted state it will throw in finalizer to tell you about it.

You can check this by printing task's status in catch block:

catch (AggregateException ex)
{ 
    Console.WriteLine("catch");
    // Will print: Status in catch: Canceled
    Console.WriteLine("Status in catch: {0}", g.Status);
}

Must I always inspect if the antecedent throw an exception ( in each line ? ) ? ( I can't check each line ! it doesn't make any sense and very annoying)

Yes you should observe antecedent tasks exception to avoid this issue:

static class TaskEx
{
    public static Task ObserverExceptions(this Task task)
    {
        task.ContinueWith(t => { var ignore = t.Exception; },
                            TaskContinuationOptions.OnlyOnFaulted);
        return task;
    }
}

And then use it as following:

var g=  Task.Factory.StartNew<int> (() => 8)
       .ContinueWith (ant =>{throw null;})
       .ObserveExceptions()
       .ContinueWith (a =>{ Console.WriteLine("OK");});

 try{
      Console.WriteLine("1");
      g.Wait();
      Console.WriteLine("2");
     }

catch (AggregateException  ex)
      {Console.WriteLine("catch"); }

UPDATE: Added solution to last bullet

Wasn't the try catch block should have swallow the exception ? ( I thought that all exceptions bubble up to the wait method....so ? )

We have set of extension method (called TransformWith) in our project that can solve this particular issue and gain following:

  1. Exception would bubble up to the catch block and
  2. We'll not crash application with TaskUnobservedException

Here the usage

var g = Task.Factory.StartNew(() => 8)
       .ContinueWith(ant => { throw null; })
       // Using our extension method instead of simple ContinueWith
       .TransformWith(t => Console.WriteLine("OK"));

try
{
    Console.WriteLine("1");
    // Will fail with NullReferenceException (inside AggregateExcpetion)
    g.Wait();
    Console.WriteLine("2");
}

catch (AggregateException ex)
{
    // ex.InnerException is a NullReferenceException
    Console.WriteLine(ex.InnerException);
}

And here is a extension method:

static class TaskEx
{
    public static Task TransformWith(this Task future, Action<Task> continuation)
    {
        var tcs = new TaskCompletionSource<object>();
        future
            .ContinueWith(t =>
            {
                if (t.IsCanceled)
                {
                    tcs.SetCanceled();
                }
                else if (t.IsFaulted)
                {
                    tcs.SetException(t.Exception.InnerExceptions);
                }
                else
                {
                    try
                    {
                        continuation(future);
                        tcs.SetResult(null);
                    }
                    catch (Exception e)
                    {
                        tcs.SetException(e);
                    }
                }
            }, TaskContinuationOptions.ExecuteSynchronously);

        return tcs.Task;
    }    
}
Sergey Teplyakov
  • 11,477
  • 34
  • 49
  • Sergey , regarding your LAST code : If I use `OnlyOnFaulted`-- my application wont die ( right? ) cause the `exception` property _has_ been inspected. and SO , I could do `throw myNewException` which will be cached in the `catch` clause ( and still my app wont die) **right** ? – Royi Namir Dec 06 '12 at 10:12
  • If you add OnlyOnFaulted in last ContinueWith you'll stil get AggregateException because g changes it state to Canceled anyway. – Sergey Teplyakov Dec 06 '12 at 10:19
  • @RoyiNamir: I changed clarification: 'Another issue that you have temporary task (task 2 in my list) that you never observe. An because you never observe it faulted state it will throw in finalizer to tell you about it.' – Sergey Teplyakov Dec 06 '12 at 10:20
  • **No**. once you swollowed the exception ( `var ignore = t.Exception;` ) you wont get exception. http://i.stack.imgur.com/s8j9n.jpg....( this is a response to your comment above ["If you add OnlyOnFaulted ...."]) – Royi Namir Dec 06 '12 at 10:26
  • Sorry, I mean if you'll add OnlyOnFault in **my code** to the last `ContinueWith` you still will fail with Canceled task. – Sergey Teplyakov Dec 06 '12 at 10:30
  • there are **2 kind of exceptions here**. one which will KILL your app. and one which will be cached in the catch. we are agree on this one right ? – Royi Namir Dec 06 '12 at 10:33
  • here is the proof http://i.stack.imgur.com/YEdA6.jpg and http://i.stack.imgur.com/PaIpS.jpg – Royi Namir Dec 06 '12 at 10:38
  • _( i wrote cache instead of catch. ignore the mistaken word.)_ – Royi Namir Dec 06 '12 at 10:39
  • @RoyiNamir: yes, I agree. There is two exception (and two failed task). Task2 will fail (with `throw null`) and will crash app if you'll not observe it. Task3 will fail if your predicated wouldn't match. `ObserveException` will save your app and in this case you can ommit TaskContinuationOption (see my code) to save third task from failure. My current code will not fail at all and it clearly stated that you're going to swallow any exceptions from parent tasks. – Sergey Teplyakov Dec 06 '12 at 10:43
  • @RoyiNamir: please take a look at my update, maybe that solution can help you. – Sergey Teplyakov Dec 06 '12 at 11:26
3
  • Do I get the AggregateException exception because I haven't inspected the Exception property ?

Tasks always throw AggregateException : http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.exception.aspx

You can get the original exception using :

var myTask = Task.Factory.StartNew(() => { throw new NotImplementedException(); });
var myException = myTask.Exception.Flatten().InnerException as NotImplementedException;
  • Must I always inspect if the antecedent throw an exception ( in each line ? ) ? ( I can't check each line ! it doesn't make any sense and very annoying)

Yes it is anoying, you should create two continuations for each task to check exceptions : one that checks if there has been an exception to handle it, and another one to continue the operation if there was no exception see TaskContinuationOptions.OnlyOnFaulted and TaskContinuationOptions.OnlyOnRanToCompletion. You should even create a third continuation to deal with cancellation if needed.

  • Wasn't the try catch block should have swallow the exception ? ( I thought that all exceptions bubble up to the wait method....so ? )

No it won't, exceptions are not thrown at higher level, you should use TaskContinuationOptions.OnlyOnFaulted on the task continuation to check if there was an exception. You can get tasks exceptions at caller's level only with the async keyword not available in .net 4
AlexH
  • 2,650
  • 1
  • 27
  • 35
  • Alex you used 3 times the word `async` and my question is tagged as fw4. I cant do anything with it .... – Royi Namir Dec 06 '12 at 10:14
  • You're right, I've updated the response. For information, you can use async with .net 4 : http://nuget.org/packages/Microsoft.Bcl.Async (but only in VS 2012) – AlexH Dec 06 '12 at 10:28
  • So even If i use `TaskContinuationOptions.OnlyOnFaulted` I still have to swallow/check it with something like `var dummy= a.Exception;` right ? (http://i.stack.imgur.com/s8j9n.jpg) – Royi Namir Dec 06 '12 at 10:30
  • If you have the TaskContinuationOptions.OnlyOnFaulted state activated, the previous task threw an unhandled exception which will be present in the previousTask.Exception : http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions.aspx – AlexH Dec 06 '12 at 10:38
  • I dont see how you last comment answered the question :-). after checking I got to conclusion that i still must swallow using the exception property. ( again there are 2 kind of exceptions here. ones that kills your app and ones that doesnt )http://i.stack.imgur.com/YEdA6.jpg http://i.stack.imgur.com/PaIpS.jpg – Royi Namir Dec 06 '12 at 10:41
  • Sorry for the delay ;) An unhandled Task exception will be thrown when the task will be collected. The exception is considered handled when you consume the result of the Task (in your case when you get a.Exception) or when you call task.Wait Take a look at this answer for more details http://stackoverflow.com/questions/7883052/a-tasks-exceptions-were-not-observed-either-by-waiting-on-the-task-or-accessi Hope this answers your question – AlexH Dec 14 '12 at 07:51
0

Handle AggregateExceptions like this:

catch(AggregateException aex)
{
    aex.Handle(ex =>
    {
       // Do some handling and logging
        return true;
    }
}
pwnyexpress
  • 1,016
  • 7
  • 14