备份捕获 EventHandler 中抛出的异常

Backup catching for exception thrown in EventHandler

我有一个 C# 程序 运行 作为 Windows 服务做一些网络恶作剧 我以为我已经设置了最后的 "Log Fatal Errors" 处理设置。但是我遇到了一个边缘案例,在这种情况下,服务最终会死掉,但会躲过这些陷阱。 :(

我认为这是由代码在注册到 .NET 库的事件的事件处理程序中抛出异常引起的。

显然我可以(并且应该!)在我的处理程序中捕获异常,但我想了解这是如何避免我的回退错误处理的,以及是否我可以添加一些更强大的回退日志记录,以确保我有一些日志记录可以在将来分析类似的无声错误。


相关代码的妙语并不复杂:

ServiceBase.Run(container.Resolve<MyProjectWindowsService>());try ...catchProgram.Main() MyProjectWindowsService : ServiceBase 是具有 OnStop() 实现的服务对象。 NetworkChange.NetworkAvailabilityChanged += CodeThatThrows;

但是当抛出该异常时,OnStop()try...catch 都不会触发。 我可以在调试器中获取它,它似乎不会 go 任何地方..它只是...停止。

下面是更完整的计划详细信息,如果您需要的话。

如何在注册到外部库事件的事件处理程序中捕获和记录未处理的异常?

(另外...我上面描述的行为是预期的行为,还是有一些奇怪的事情发生?)


程序 EXE 入口点:

using System;
using System.ServiceProcess;
using Autofac;
using Autofac.Extras.NLog;
using NLog;

namespace MyProject.Service
{
    internal static class Program
    {
        private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();

        /// <summary>
        ///     The main entry point for the application.
        /// </summary>
        private static void Main()
        {
            try
            {
                // var container = ...DI Setup...
                ServiceBase.Run(container.Resolve<MyProjectWindowsService>());
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Unexpected error");
            }
            finally
            {
                Logger.Info("==========================");
                Logger.Info("WindowsService Stopped (2)");
                Logger.Info("==========================");
            }

        }
    }
}

服务对象

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 注册并最终调用:

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

不幸的是,我只能推测为什么您的代码没有捕获异常(并且我将这种推测保留在评论中)

但是有 2 个事件可能对您有帮助,

AppDomain.UnhandledException - 这允许您为应用程序中任何未处理的异常注册一个全局处理程序。这是文档 https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.unhandledexception?view=netframework-4.8

TaskScheduler.UnobservedTaskException - 我将其包括在内,因为我不熟悉您正在使用的框架库的内部结构,但可能会发生一些异步代码在某个地方,这可能不会观察到任务的结果。如果错误的任务(即抛出异常)从未被等待或从未访问过 Result 属性 然后超出范围因此可以被垃圾收集;在未来某个不确定的时间点,它将被收集并抛出 UnobservedTaskException。订阅此事件将使您能够处理该场景。文档在这里

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

深入了解 WPF 应用程序中捕获的内容:

 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);
                                      }
                                  });   

输出将是:

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

所以 FirstChanceException 将捕获所有内容(甚至是已处理的),其余的将不会捕获任何内容。我的建议是像这样修改您的示例:

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
            }

        }
    }