Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TaskScheduler.UnobservedTaskException never gets called

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("UNOBSERVED EXCEPTION");
    }
}

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?

like image 707
Nathan Ridley Avatar asked Oct 19 '10 22:10

Nathan Ridley


2 Answers

To me, TaskScheduler.UnobservedTaskException at first gives a very wrong sense of security. It's really not worth much if it depends on Garbage Collection.

I found the following solution, taken from this msdn article, to be much more reliable. It basically executes the continuation block (where you log the exception) only if there were unhandled exceptions in task1, and does not block UI execution.

You might also want to flatten nested AggregateExceptions and perhaps create a extension method, as Reed Copsey depicted here.

var task1 = Task.Factory.StartNew(() =>
{
    throw new MyCustomException("Task1 faulted.");
})
.ContinueWith((t) =>
{
    Console.WriteLine("I have observed a {0}",
        t.Exception.InnerException.GetType().Name);
},
TaskContinuationOptions.OnlyOnFaulted);
like image 152
Mike Fuchs Avatar answered Sep 18 '22 14:09

Mike Fuchs


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("UNOBSERVED EXCEPTION");
        }
    }
}

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 "UNOBSERVED EXCEPTION" every time prior to shutting down.

like image 27
Reed Copsey Avatar answered Sep 19 '22 14:09

Reed Copsey