5

Using Visual Studio 2015, targeting FW 4 (testing unobservable exceptions under FW 4):

enter image description here

I'm expecting this code:

static void Main(string[] args)
{

    try
    {
        Task.Factory.StartNew(() => Console.WriteLine(1))
        .ContinueWith(t => Thread.Sleep(1000))
        .ContinueWith(t => Console.WriteLine(2))
        .ContinueWith(t => Thread.Sleep(1000))
        .ContinueWith(t => { throw new Exception("aaaa"); })
        .ContinueWith(t => Console.WriteLine(3));
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);

    }
    GC.Collect();
    GC.Collect();
    Console.ReadLine();
}

To show me the exception.

I know I can see it via T.Wait() or in the last task with t.Exception - but why am I not seeing the exception here?

I know that the exception handling mechanism was changed in 4.5 and in order to get the old mechanism I should add:

 <ThrowUnobservedTaskExceptions enabled="true"/>

Which I did:

<?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>

But still the result is:

enter image description here

Question:

Why don't I see the exception?

Worth a mention that I do see the exception in debug mode:

enter image description here

icedwater
  • 4,701
  • 3
  • 35
  • 50
Royi Namir
  • 144,742
  • 138
  • 468
  • 792

2 Answers2

4

You cannot expect this code to throw an exception because all that the statements in the try clause do is describe a continuation pattern. The StartNew and ContinueWith methods do not thrown an exception. So the code execution has long left this try statement when the exception in a background task is thrown. When the exception is thrown around 2 seconds later after the program has started the execution is halted on the Console.Readline statement.

As you have already discovered you will need to wait for the tasks to finish before being able to access the exception or inside the continuation itself.

Now the reason why your application doesn't die with an unobserved exception is because Garbage Collection hasn't occurred. The unobserved exception will tear down your application domain when the Task is garbage collected. But by the time you are forcing GC, the exception hasn't yet been thrown neither the task has finished so it is not eligible for Garbage Collection.

Make sure that you place your GC.Collect calls after the Console.ReadLine method followed by yet another Console.ReadLine:

Task.Factory.StartNew(() => Console.WriteLine(1))
    .ContinueWith(t => Thread.Sleep(1000))
    .ContinueWith(t => Console.WriteLine(2))
    .ContinueWith(t => Thread.Sleep(1000))
    .ContinueWith(t => { throw new Exception("aaaa"); })
    .ContinueWith(t => Console.WriteLine(3));

Console.ReadLine();
GC.Collect();
GC.Collect();
Console.ReadLine();

Obviously the following config switch should be present:

<ThrowUnobservedTaskExceptions enabled="true"/>
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
0

Have you tried hooking up to the TaskScheduler.UnobservedTaskException is it being caught there?

[HandleProcessCorruptedStateExceptions]
public static void SetupUnobservedTaskExceptionHandling(ILogger logger)
{
    logger.Debug("Setting up unobserved task exception handling...");
    TaskScheduler.UnobservedTaskException += (sender, args) =>
    {
        var e = args.Exception;
        logger.ErrorFormat("TaskScheduler Unobserved Exception - Message: {0}", e.Message);
        logger.ErrorFormat("TaskScheduler Unobserved Exception - Inner exception: {0}", e.InnerException);
        logger.ErrorFormat("TaskScheduler Unobserved Exception - Inner exceptions: {0}", e.InnerExceptions);
        logger.ErrorFormat("TaskScheduler Unobserved Exception - StackTrace: {0}", e.StackTrace);
        args.SetObserved();
    };
}

If you want to handle it off the task itself you could also use something like:

/// <summary>
/// Handles all the exceptions thrown by the <paramref name="task"/>
/// </summary>
/// <param name="task">The task which might throw exceptions.</param>
/// <param name="exceptionsHandler">The handler to which every exception is passed.</param>
/// <returns>The continuation task added to <paramref name="task"/>.</returns>
public static Task HandleExceptions(this Task task, Action<Exception> exceptionsHandler)
{
    return task.ContinueWith(t =>
    {
        var e = t.Exception;

        if (e == null) { return; }

        e.Flatten().Handle(ie =>
        {
            exceptionsHandler(ie);
            return true;
        });
    },
    CancellationToken.None,
    TaskContinuationOptions.ExecuteSynchronously,
    TaskScheduler.Default);
}
  • Adding the first method , still doesn't show the exception. Regarding the second code - Thanks . – Royi Namir Jan 09 '16 at 16:07
  • Just tried this on LINQPad, your code throws the exception. and adding the methods above both catches it as well. I think your problem is the `Console.ReadLine` stopping it from being thrown. –  Jan 09 '16 at 16:09