7

I'm trying to run the following code:

class Program
{
    static void Main(string[] args)
    {
        var task = Task.Factory.StartNew(() =>
            {
                throw new ApplicationException("message");
            });
        try
        {
            task.ContinueWith(t => Console.WriteLine("End"));
        }
        catch (AggregateException aex)
        {
            Console.Write(aex.InnerException.Message);
        }
    }
}

I expected that the Exception would be caught in the following location:

        catch (AggregateException aex)
        {
            Console.Write(aex.InnerException.Message);
        }

But this is not happening. Why is this so?

svick
  • 236,525
  • 50
  • 385
  • 514
sribin
  • 1,736
  • 1
  • 13
  • 21
  • The answers addressed what and how to do but not the "why" (the exception is not caught). I believe this rationale is important to understand and it is explained in Stephen Toub's article [Task Exception Handling in .NET 4.5](http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/10217876.aspx) which is a must-read] – Gennady Vanin Геннадий Ванин May 17 '13 at 03:23

4 Answers4

10

You're just printing out the task - which won't even have completed yet.

Printing out the task doesn't wait for it to complete, or try to fetch the value.

If you change your code to:

try
{
    task.Wait();
}

... then I'd expect it to catch the exception.

(I was previously using Task<T>.Result, but I notice this is a task with no return value, so it would just be the non-generic Task.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • @NominSim No. The delegate itself won't have been run yet when `StartNew` returns. It can't throw an exception on that line when the exception hasn't even been reached yet. – Servy May 14 '13 at 17:11
  • 1
    @NominSim: It's caught by the blocking `Result` property fetch. – Jon Skeet May 14 '13 at 17:11
  • 1
    @NominSim The `ApplicationException` is thrown from the other thread. In that thread, that is calling the anonymous delegate provided, it will eventually be caught (in a catch block somewhere in the internals of the `Task` code). It is not re-thrown from the main thread when the task is created. When the main thread then waits on the task, once the task completes, it will notice that the task ended with a top level exception; that exception is then re-thrown (in a wrapped `AggregateException`) in the main thread because that's what the `Wait` method's implementation explicitly does. – Servy May 14 '13 at 17:22
  • 1
    @NominSim Are you debugging in VS? If so, you may see VS breaking on a first-chance exception (that is then caught and stored by the `Task`.) Under normal execution conditions, the `ApplicationException` will never be thrown outside the `try/catch` block. – dlev May 14 '13 at 17:23
  • @NominSim: As far as i understood, t.ContinueWith waits for the task to complete but then does not access its result; if it did an exception would be thrown. This continuation executes in any case (of success or error) – Luis Filipe May 14 '13 at 17:23
  • @LuisFilipe That would depend on it's [TaskContinuationOptions](http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions.aspx) – Nick Freeman May 14 '13 at 17:32
1

The way Task works, the code that ends up calling the delegate you pass to StartNew will be caught, eventually, and the Exception will be stored in an instance field of the task. That exception can be inspected by looking at the task.Exception property. The line Console.WriteLine(task) is just calling task.ToString internally. That method won't result in the exception being thrown or re-thrown.

However, under certain circumstances the exception that is caught will be re-thrown. Two examples are when accessing Result and calling Wait, as well as when you await a task in C# 5.0.

The following code:

try
{
    task.Wait();
}
catch (AggregateException aex)
{
    Console.Write(aex.InnerException.Message);
}

Will result in the stored exception being re-thrown and the exception message will be printed.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Except that even with a semi-colon, you can't use a property access as a statement on its own. – Jon Skeet May 14 '13 at 17:12
  • @LuisFilipe I've already edited the mistake he's referring to. Just see the revision history. – Servy May 14 '13 at 17:25
  • @LuisFilipe Actually, I fixed it within the 5 minute window, so it won't even be in the revision history; but I had a mistake that I have since fixed. – Servy May 14 '13 at 17:26
-2

AggregateException and ApplicationException are both children of the same class, System.Exception. AggregateException is-not a ApplicationException

TruthOf42
  • 2,017
  • 4
  • 23
  • 38
  • 1
    This is irrelevant. No exception of any kind is ever thrown from the OP's `try` block. – Servy May 14 '13 at 17:15
  • Yeah, I am going to go out on a limb and say that `throw new ApplicationException("message");` was not the real implementation of this task. It could have been a typo in creating the example and have nothing to do with the real problem. – Nick Freeman May 14 '13 at 17:18
  • @NickFreeman Nope. That's not true at all. The `Task` class will wrap any exception thrown from within it's body in an `AggregateException`, so the use of that exception is appropriate. The issue, as I describe in my answer, is that the OP needs to use `task.Wait()`. That little change results in the program working exactly as intended. – Servy May 14 '13 at 17:23
  • @Servy Interesting. I wasn't disagreeing with you, I was agreeing with you. I stand by everything I said except for the typo part then :P. I still imagine that OP's goal was not to have an all powerful exception task. I was just saying the exception thrown was irrelevant to the quesiton. – Nick Freeman May 14 '13 at 17:28
-4

Cause your statement is not in the try, but before it... catch will catch every exception from within the try curly brackets...

Laurent S.
  • 6,816
  • 2
  • 28
  • 40
  • 3
    I think you've misunderstood the nature of tasks. – Jon Skeet May 14 '13 at 17:12
  • This is simply not true. An easy counter-example: set the `try` block's body to be `task.Result` and it will indeed end up re-throwing the exception generated in the delegate that defines the task. – Servy May 14 '13 at 17:12
  • @Jon Skeet > Seeing the downvotes, I think I've misunderstood the whole question indeed :-) – Laurent S. May 14 '13 at 17:13