views:

8

answers:

1

Based on my research, I have learned the following:

  1. TaskScheduler.UnobservedTaskException must wait for the task to be garbage collected before that task's unobserved exception will bubble up to the UnobservedTaskException event.
  2. If you're using Task.Wait(), it'll never get called anyway, because you're blocking on an impending result from the Task, hence the exception will be thrown on Task.Wait() rather than bubble up to the UnobservedException event.
  3. Calling GC.Collect() manually is generally a bad idea unless you know exactly what you're doing, hence it's good in this case for confirming things, but not as a proper solution to the issue.

The Problem

If my application exits before the garbage collector kicks in, I absolutely 100% cannot get my UnobservedTaskException event to fire.

Note the following code:

class Program
{
    static void Main(string[] args)
    {
        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
        Task.Factory.StartNew(() =>
        {
            Console.WriteLine("Task started.");
            throw new Exception("Test Exception");
        });
        Thread.Sleep(1000);
        //GC.Collect();
        //GC.WaitForPendingFinalizers();
    }

    static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
    {
        File.WriteAllText(@"C:\data\TestException.txt", e.Exception.ToString());
        Console.WriteLine("EXCEPTION UNOBSERVED");
    }
}

No exception file is written and nothing is written to the console. 10-15 minutes and more can go by after the application exits and still I see no evidence that my application's remains were garbage collected. You might ask, well why not just collect on exit? Well, my real world scenario is that my exception trapping runs inside a WCF service hosted inside a Windows service. I cannot trap when the Windows service is shutting down (and hence manually call GC.Collect()) because there is no event for that as far as I can see.

Where am I going wrong? How do I ensure that if something deep inside the WCF service is going to ultimately break my windows service, that I have a chance to log the exception before the service falls over?

+1  A: 

Nathan,

You're points are all true. Try the following:

namespace ConsoleApplication1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
            Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Task started.");
                throw new Exception("Test Exception");
            });

            Thread.Sleep(1000);
            Console.WriteLine("First Collect");
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Console.WriteLine("Waiting");
            Console.ReadKey();
        }

        static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            Console.WriteLine("EXCEPTION UNOBSERVED");
        }
    }
}

I have noticed that the debugger often "traps" the UnobservedTaskException event, causing it to not fire appropriately. Run this outside of the debugger, and it will print "EXCEPTION UNOBSERVED" every time prior to shutting down.

Reed Copsey
Hi, thanks for the suggestion, although I am actually already running it outside of the debugger. I notice also you're using GC.Collect(). What happens in your example if you do not manually collect?
Nathan Ridley
@Nathan: Normally, there's not enough GC pressure to ever have this happen in this simple example. If the GC doesn't collect, the "task" won't hit the finalizer, and the UnobservedException never fires. It only happens when the finalizer collects an unrooted task with an exception.
Reed Copsey
@Nathan: Note that UnobservedTaskException won't prevent the app from shutting down in any case - you need to handle your exceptions inside of your task, or always wait on a task, if you want to prevent this from shutting down an app.
Reed Copsey
So the question remains, when you shift my code inside a WCF service hosted inside a windows service, how do I get the event to fire so that if something unexpected takes down the service, I have a log file to go on?
Nathan Ridley
I don't need the application to stay running, I only need to make sure the exception is logged so that I have an idea what caused the crash.
Nathan Ridley
@Nathan: You can use UnobservedTaskException as above. If the task takes down the service, it'll get logged. The only time this won't happen is if the task has an exception, but the service is shut down cleanly prior to the garbage collector call. If you want to force it, you could do a GC.Collect to force it - but I wouldn't recommend it (I'd let that very unlikely scenario go).
Reed Copsey
Your above code invokes GC.Collect(), and your subsequent comments talk about GC pressure resulting in the garbage collector not collecting. I already have a real world commercial service running and it crashes every few days without any form of logging whatsoever. For the life of me, I cannot figure out why AppDomain.UnhandledException will fire, but TaskScheduler.UnobservedException will not.
Nathan Ridley
@Nathan: Are you getting any messages in the system event logs on the system? (My code was calling GC.Collect just to force the issue, in order to show you that it does work. You could add that to your service during it's shutdown, but it won't have an effect if it's "crashing", only if it was shut down cleanly.)
Reed Copsey
The event log shows that the service crashed, but doesn't contain any useful information that would give me a starting point to track down the problem. Would it be useful to put `GC.Collect()` in the `Dispose` method of the windows service class?
Nathan Ridley
@Nathan: It won't have an effect if the service **crashes**, since Dispose() would never get called in that situation. It would only have an effect if the service is shutdown nicely, but still had a task sitting around with a problem.
Reed Copsey
In the end, I still have no way to guarantee that an unhandled exception thrown inside my service gets logged.
Nathan Ridley
@Nathan: If it's happening from the task, AND if that's crashing the service, AND if the logging doesn't take too long, it should happen with the event handler.
Reed Copsey
I'll have to tweak and play around with things a bit I think. I wish there was an answer to the original question above that works without forcing a call to GC.Collect(). Thanks for your help on this one in any case.
Nathan Ridley