70

Please have a look at the following code-

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);
    Console.ReadKey();
    Console.WriteLine("Hello");
}

private static int div(int x, int y)
{
    if (y == 0)
    {
        throw new ArgumentException("y");
    }
    return x / y;
}

If I execute the code in release mode, The output is "One or more errors occurred" and once I hit the "Enter key, "Hello" is also getting displayed. If I run the code in debug mode, the output is same as release mode. But when debugging in IDE, A IDE exception message ("Unhandled exception in user code" ) appears when the control executes the line

throw new ArgumentException("y"); 

If I continue from there on, the program does not crash and displays the same output as release mode. Is this proper way to handle exception?

avo
  • 10,101
  • 13
  • 53
  • 81
Anirban Paul
  • 1,065
  • 1
  • 16
  • 22
  • Consider switching to async/await: It is an easier syntax to write and read, especcially when handling exceptions. –  Feb 03 '14 at 07:28
  • @AnirbanPaul, you may want to update the question with your requirements: VS2010 and .Net 4.0, as you did [here](http://stackoverflow.com/a/21521111/1768303). – noseratio Feb 03 '14 at 08:18

6 Answers6

87

You probably don't need separate OnlyOnFaulted and OnlyOnRanToCompletion handlers, and you're not handling OnlyOnCanceled. Check this answer for more details.

But when debugging in IDE, A IDE exception message ("Unhandled exception in user code" ) appears when the control executes the line

You see the exception under debugger because you probably have enabled it in Debug/Exceptions options (Ctrl+Alt+E).

If I continue from there on, the program does not crash and displays the same output as release mode. Is this proper way to handle exception?

An exception which was thrown but not handled inside a Task action will not be automatically re-thrown. Instead, it be wrapped for future observation as Task.Exception (of type AggregateException). You can access the original exception as Exception.InnerException:

Exception ex = task.Exception;
if (ex != null && ex.InnerException != null)
    ex = ex.InnerException;

To make the program crash in this case, you actually need to observe the exception outside the task action, e.g. by referencing the Task.Result:

static void Main(string[] args)
{
    // Get the task.
    var task = Task.Factory.StartNew<int>(() => { return div(32, 0); });

    // For error handling.
    task.ContinueWith(t => { Console.WriteLine(t.Exception.Message); }, 
        TaskContinuationOptions.OnlyOnFaulted);

    // If it succeeded.
    task.ContinueWith(t => { Console.WriteLine(t.Result); }, 
        TaskContinuationOptions.OnlyOnRanToCompletion);

    Console.ReadKey();

    Console.WriteLine("result: " + task.Result); // will crash here

    // you can also check task.Exception

    Console.WriteLine("Hello");
}

More details: Tasks and Unhandled Exceptions, Task Exception Handling in .NET 4.5.

Updated to address the comment: here is how I would do this in a UI app with .NET 4.0 and VS2010:

void Button_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => 
    {
        return div(32, 0); 
    }).ContinueWith((t) =>
    {
        if (t.IsFaulted)
        {
            // faulted with exception
            Exception ex = t.Exception;
            while (ex is AggregateException && ex.InnerException != null)
                ex = ex.InnerException;
            MessageBox.Show("Error: " + ex.Message);
        }
        else if (t.IsCanceled)
        {
            // this should not happen 
            // as you don't pass a CancellationToken into your task
            MessageBox.Show("Canclled.");
        }
        else
        {
            // completed successfully
            MessageBox.Show("Result: " + t.Result);
        }
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

For as long as you target .NET 4.0 and you want the .NET 4.0 behavior for unobserved exceptions (i.e., re-throw when task gets garbage-collected), you should explicitly configure it in the app.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <runtime>
    <ThrowUnobservedTaskExceptions enabled="true"/>
  </runtime>
</configuration>

Check this for more details:

Unobserved task exceptions in .NET4

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • I am using VS2010 and .Net 4.0. In actual scenario, I would be applying same kind of code in a Winform application. On OnlyOnFaulted part I intend to display error message and log the exception and on OnlyOnRanToCompletion part I intend to update the UI with help of CurrentSynchronizationContext. What I am worried about is the appearance of exception message while debugging. Does that mean that the exception is not handled? Please let me know whether the above said code pattern is a safe and efficient way to handle task exception? Also please suggest if there is any better way to do that. – Anirban Paul Feb 03 '14 at 06:56
  • 1
    If you know you have an AggregateException, wouldn't it be easier to use the [Flatten()](https://msdn.microsoft.com/en-us/library/system.aggregateexception.flatten.aspx) method? – takrl Sep 13 '16 at 11:39
  • @takrl, I think it depends on whether you need all of the aggregated exceptions. See [this](http://stackoverflow.com/q/18314961/1768303). – noseratio Sep 13 '16 at 19:02
  • Your first example is missing; `task = task.ContinueWith()` since you extended the original task – Joel Harkes Sep 22 '17 at 08:50
  • @JoelHarkes, not sure I follow... which line exactly are you reffering to? – noseratio Sep 22 '17 at 10:05
  • @Noseratio the first big example uses `task.ContinueWith()` twice, but the result is not used. the result is a new task which when awaited (in example done with `task.Result`. in this example the Continue with tasks still run after `task.Result` is called. – Joel Harkes Sep 22 '17 at 11:04
  • @JoelHarkes, no I didn't want another `ContinueWith` for that context. `task.Result` will simply block until completion (regardless of the completion status), and that's sufficient for such a simple example. – noseratio Sep 25 '17 at 10:37
  • @Noseratio you are missing my point. `task.Result` will wait for: `return div(32, 0);` BUT NOT FOR: ` Console.WriteLine(t.Exception.Message);` or `Console.WriteLine(t.Result);` this will probably run after or synchronous no way of nowing, since you don't save this new task. in this case not a big issue, but can lead to serious problem if people do not realize. wait ill make an edit to your code so you can see. – Joel Harkes Sep 26 '17 at 08:25
  • 1
    @JoelHarkes, the purpose of the first fragment of code was just to show the OP how to make his original code to do what he wanted. It wasn't there to show how to do it right, that's done in the second fragment. Your [edited version](https://stackoverflow.com/revisions/21521111/10) woudn't even compile as is, because `task = task.ContinueWith()` will return a `Task` there rather than a `Task` so there's simply no `Task.Result` to observe. **Please refrain from editing it, post your own answer if you like**. – noseratio Sep 26 '17 at 12:33
  • relax, it was just to show what i meant, and yes you have a point there. – Joel Harkes Sep 27 '17 at 07:14
7

What you have there is an AggregateException. This is thrown from tasks and requires you to check the inner exceptions to find specific ones. Like this:

task.ContinueWith(t =>
{
    if (t.Exception is AggregateException) // is it an AggregateException?
    {
        var ae = t.Exception as AggregateException;

        foreach (var e in ae.InnerExceptions) // loop them and print their messages
        {
            Console.WriteLine(e.Message); // output is "y" .. because that's what you threw
        }
    }
},
TaskContinuationOptions.OnlyOnFaulted);
Simon Whitehead
  • 63,300
  • 9
  • 114
  • 138
  • In actual scenario, I would be applying same kind of code in a Winform application. On OnlyOnFaulted part I intend to display error message and log the exception and on OnlyOnRanToCompletion part I intend to update the UI with help of CurrentSynchronizationContext. What I am worried about is the appearance of exception message while debugging. Does that mean that the exception is not handled? Please let me know whether the above said code pattern is a safe and efficient way to handle task exception? Also please suggest if there is any better way to do that. – Anirban Paul Feb 03 '14 at 06:41
  • 5
    `t.Exception` is declared with type `AggregateException`, so it suffices to test for nullness. The `as` operation isn't required. – Drew Noakes Jul 01 '15 at 14:59
3

As of .Net 4.5, you can use AggregateException.GetBaseException() to return " the root cause of this exception".

https://learn.microsoft.com/en-us/dotnet/api/system.aggregateexception.getbaseexception?view=netframework-4.7.2

The documentation seems to be a little off though. It claims to return another AggregateException. However I think you'll find that it returns the ArgumentException that was thrown.

Cody Barnes
  • 358
  • 3
  • 8
0

The "One or more errors occurred" comes from a wrapper exception that is made by the task pool. Use Console.WriteLine(t.Exception.ToString()) to print the whole exception if you need it.

IDEs may automatically capture all exceptions no matter whether they were handled or not.

hsun324
  • 549
  • 3
  • 9
0

Since you are using tasks you should get AggregateException which wraps all exceptions occured during execution. You see One or more errors occurred message, because it's default output of AggregateException.ToString() method.

You need Handle method of the exception's instance.

Also see the right approach to handle such exceptions here.

Tony
  • 7,345
  • 3
  • 26
  • 34
-5
        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;
                });
        }
  • 1
    -1 because your exception will never be caught, as using *await* 'un-wraps' the AggregateException, so there will be no AggregateExceptions to catch – controlbox Aug 21 '17 at 09:42