简体   繁体   中英

Backup catching for exception thrown in EventHandler

I have a C# Program running as a Windows service doing some Network shenanigans I thought I had last-ditch "Log Fatal Errors" handling set up. But I've come across an edge case where the Service ends up dead but dodges those catches. :(

I believe this is caused by code throwing an Exception in the EventHandler registered to a .NET library's event.

Obviously I can (and should !) catch the Exception in my handler, but I'd like to understand how this is avoiding my fall-back error handling, and whether I can add some even more robust fall back logging, to ensure that I have some log records to analyse similar silent bugs in future.


The punchline of relevant code isn't terribly complex:

ServiceBase.Run(container.Resolve<MyProjectWindowsService>()); in a try ...catch in Program.Main() MyProjectWindowsService : ServiceBase is the service object with an OnStop() implmentation. NetworkChange.NetworkAvailabilityChanged += CodeThatThrows;

But when that Exception is thrown, neither OnStop() nor the try...catch trigger. I can get it in a debugger, and it doesn't seem to go anywhere .. it just ... stops.

Fuller program details below, if you want them.

How can I catch and log unhandled exceptions in Event Handlers registered to external library events?

(Also ... Is the behaviour I've described above the expected behaviour, or is there something slightly weird happening?)


Program EXE entry point:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using NLog;
using Exception = System.Exception;

namespace DNSProxy.Service
{

    public class NetworkService
    {
        public NetworkService()
        {
        }

        public bool NetworkDetectionEnabled
        {
            set
            {
                if (value)
                {
                    NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged;
                    NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged;
                }
                else
                {
                    NetworkChange.NetworkAvailabilityChanged -= OnNetworkAvailabilityChanged;
                    NetworkChange.NetworkAddressChanged -= OnNetworkAddressChanged;
                }
            }
        }

        private void OnNetworkAddressChanged(object sender, EventArgs e)
        {
            CodeThatCanApparentlyThrow();
        }

        private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            CodeThatCanApparentlyThrow();
        }
    }
}

Service Object

using System; using System.ServiceModel; using System.ServiceProcess; using Autofac; using Autofac.Integration.Wcf; using NLog; namespace MyProject.Service { public class MyProjectWindowsService : ServiceBase { private readonly ILogger _logger; public DNSProxyWindowsService(ILogger logger) { ServiceName = Constants.SERVICE_NAME; _logger = logger; } protected override void OnStart(string[] args) { _logger.Info("=============================="); _logger.Info("DNProxy WindowsService Started"); _logger.Info("=============================="); //Other Active setupsteps } protected override void OnStop() { try { //Other Active shutdown steps. } catch (Exception ex) { _logger.Error(ex, "Could not shut down service tidily"); } finally { _logger.Info("=========================="); _logger.Info("WindowsService Stopped (1)"); _logger.Info("=========================="); } } } }

EventListener Registered to and ultimately invoked:

 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using NLog; using Exception = System.Exception; namespace DNSProxy.Service { public class NetworkService { public NetworkService() { } public bool NetworkDetectionEnabled { set { if (value) { NetworkChange.NetworkAvailabilityChanged += OnNetworkAvailabilityChanged; NetworkChange.NetworkAddressChanged += OnNetworkAddressChanged; } else { NetworkChange.NetworkAvailabilityChanged -= OnNetworkAvailabilityChanged; NetworkChange.NetworkAddressChanged -= OnNetworkAddressChanged; } } } private void OnNetworkAddressChanged(object sender, EventArgs e) { CodeThatCanApparentlyThrow(); } private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) { CodeThatCanApparentlyThrow(); } } }

I unfortunately can only speculate why the exception isn't being caught by your code (and I've kept that speculation to the comments)

However 2 events that might help you are,

AppDomain.UnhandledException - this allows you to register a global handler for any unhandled exceptions in your application. Here is the documentation https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.unhandledexception?view=netframework-4.8

TaskScheduler.UnobservedTaskException - I've included this as I'm not familiar with the internals of the framework libraries you are using, but there maybe some asynchronous code happening somewhere, that is potentially not observing the result of a task. If a faulted task (ie an exception was thrown) is never awaited or never has the Result property accessed and then goes out of scope so it can be garbage collected; at some indeterminate point in the future, it will get collected and an UnobservedTaskException will get thrown. Subscribing to this event, will let you handle that scenario. Documentation here

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler.unobservedtaskexception?view=netframework-4.8

A little bit dig out what catches what on a WPF application :

 var domain  = AppDomain.CurrentDomain;
 domain.UnhandledException += (o, args) =>
                                         {
                                             Debug.WriteLine("Catched in UnhandledException");
                                             Debug.WriteLine(args.ExceptionObject);
                                         };

  domain.FirstChanceException += (o, args) =>
                                           {
                                               Debug.WriteLine("Catched in FirstChanceException");
                                               Debug.WriteLine(args.Exception);
                                           };

TaskScheduler.UnobservedTaskException += (o, args) =>
                                                     {
                                                         Debug.WriteLine("Catched in UnobservedTaskException");
                                                         Debug.WriteLine(args.Exception);
                                                     };
Task.Factory.StartNew(async () =>
                                  {
                                      Debug.WriteLine("Workinf");
                                      await Task.Delay(1000);
                                      try
                                      {
                                          throw new Exception("oops");
                                      }
                                      catch (Exception exception)
                                      {

                                          throw new Exception("oopps catched", exception);
                                      }
                                  });   

The output will be :

Exception thrown: 'System.Exception' in WpfApp1.exe
Catched in FirstChanceException
System.Exception: oops
   at ....
Exception thrown: 'System.Exception' in WpfApp1.exe
Catched in FirstChanceException
System.Exception: oopps catched ---> System.Exception: oops
   at ...
   --- End of inner exception stack trace ---
   at ...

So FirstChanceException will be catching everything (even the handled ones) and the rest won't be catching anything. My suggestion is modifying your example like this:

public class NetworkService
    {
        private readonly SynchronizationContext currContext;
        public NetworkService()
        {
            this.currContext = SynchronizationContext.Current;
        }

        private void OnNetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            try
            {
                CodeThatCanApparentlyThrow();
            }
            catch (Exception exception)
            {
                this.currContext.Post(s => throw exception, null); // this will propagate your exception into main thread
            }

        }
    }  

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM