【问题标题】:.NET Global exception handler in console application.NET 控制台应用程序中的全局异常处理程序
【发布时间】:2011-03-09 04:08:43
【问题描述】:

问题:我想在我的控制台应用程序中为未处理的异常定义一个全局异常处理程序。在asp.net中,可以在global.asax中定义一个,在windows应用程序/服务中,可以如下定义

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyExceptionHandler);

但是如何为控制台应用程序定义全局异常处理程序?
currentDomain 似乎不起作用(.NET 2.0)?

编辑:

啊,愚蠢的错误。
在VB.NET中,需要在currentDomain前面加上“AddHandler”关键字,否则在IntelliSense中看不到UnhandledException事件...
这是因为 VB.NET 和 C# 编译器对事件处理的处理方式不同。

【问题讨论】:

    标签: c# .net vb.net exception-handling console-application


    【解决方案1】:

    不,这是正确的做法。这完全按照它应该的方式工作,您也许可以从中工作:

    using System;
    
    class Program {
        static void Main(string[] args) {
            System.AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionTrapper;
            throw new Exception("Kaboom");
        }
    
        static void UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e) {
            Console.WriteLine(e.ExceptionObject.ToString());
            Console.WriteLine("Press Enter to continue");
            Console.ReadLine();
            Environment.Exit(1);
        }
    }
    

    请记住,您无法以这种方式捕获由抖动生成的类型和文件加载异常。它们发生在您的 Main() 方法开始运行之前。捕捉这些需要延迟抖动,将有风险的代码移动到另一个方法中并对其应用 [MethodImpl(MethodImplOptions.NoInlining)] 属性。

    【讨论】:

    • 我实现了您在此处提出的建议,但我不想退出应用程序。我只想记录它,并继续该过程(没有Console.ReadLine() 或任何其他程序流程干扰。但我得到的是一次又一次地重新引发异常。
    • @Shahrooz Jefri:一旦遇到未处理的异常,您将无法继续。堆栈搞砸了,这是终端。如果您有服务器,您可以在 UnhandledExceptionTrapper 中执行的操作是使用相同的命令行参数重新启动程序。
    • 肯定有!我们不是在这里讨论 Application.ThreadException 事件。
    • 我认为理解这个答案和 cmets 回复的关键是要意识到这段代码现在演示了检测异常,但没有像在正常的 try/catch 中那样完全“处理”它阻止您可以选择继续。有关详细信息,请参阅我的其他答案。如果您以这种方式“处理”异常,那么当另一个线程上发生异常时,您将无法继续运行。从这个意义上说,如果“处理”的意思是“处理并继续运行”,则可以说这个答案并没有完全“处理”异常。
    • 更不用说有很多技术可以在不同的线程中运行。例如,任务并行库 (TPL) 有自己的方法来捕获未处理的异常。因此,说这并不适用于所有情况有点荒谬,C# 中没有一个地方可以包罗万象,但根据您使用的技术,您可以在不同的地方使用。
    【解决方案2】:

    如果您有一个单线程应用程序,您可以在 Main 函数中使用简单的 try/catch,但是,这不包括可能在 Main 函数之外,在其他线程上抛出的异常,例如(如在其他 cmets 中注明)。此代码演示了即使您尝试在 Main 中处理异常,异常如何导致应用程序终止(请注意,如果您按 Enter 并允许应用程序在异常发生之前正常退出,但如果您让它运行,程序如何优雅退出,它以非常不愉快的方式终止):

    static bool exiting = false;
    
    static void Main(string[] args)
    {
       try
       {
          System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
          demo.Start();
          Console.ReadLine();
          exiting = true;
       }
       catch (Exception ex)
       {
          Console.WriteLine("Caught an exception");
       }
    }
    
    static void DemoThread()
    {
       for(int i = 5; i >= 0; i--)
       {
          Console.Write("24/{0} =", i);
          Console.Out.Flush();
          Console.WriteLine("{0}", 24 / i);
          System.Threading.Thread.Sleep(1000);
          if (exiting) return;
       }
    }
    

    当另一个线程抛出异常以在应用程序退出之前执行一些清理时,您可以收到通知,但据我所知,如果您不这样做,您不能从控制台应用程序强制应用程序继续运行处理抛出异常的线程上的异常,而不使用一些晦涩的兼容性选项来使应用程序的行为与使用 .NET 1.x 时一样。此代码演示了如何通知主线程来自其他线程的异常,但仍会不愉快地终止:

    static bool exiting = false;
    
    static void Main(string[] args)
    {
       try
       {
          System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
          AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
          demo.Start();
          Console.ReadLine();
          exiting = true;
       }
       catch (Exception ex)
       {
          Console.WriteLine("Caught an exception");
       }
    }
    
    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
       Console.WriteLine("Notified of a thread exception... application is terminating.");
    }
    
    static void DemoThread()
    {
       for(int i = 5; i >= 0; i--)
       {
          Console.Write("24/{0} =", i);
          Console.Out.Flush();
          Console.WriteLine("{0}", 24 / i);
          System.Threading.Thread.Sleep(1000);
          if (exiting) return;
       }
    }
    

    所以在我看来,在控制台应用程序中处理它的最简洁方法是确保每个线程在根级别都有一个异常处理程序:

    static bool exiting = false;
    
    static void Main(string[] args)
    {
       try
       {
          System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
          demo.Start();
          Console.ReadLine();
          exiting = true;
       }
       catch (Exception ex)
       {
          Console.WriteLine("Caught an exception");
       }
    }
    
    static void DemoThread()
    {
       try
       {
          for (int i = 5; i >= 0; i--)
          {
             Console.Write("24/{0} =", i);
             Console.Out.Flush();
             Console.WriteLine("{0}", 24 / i);
             System.Threading.Thread.Sleep(1000);
             if (exiting) return;
          }
       }
       catch (Exception ex)
       {
          Console.WriteLine("Caught an exception on the other thread");
       }
    }
    

    【讨论】:

    • TRY CATCH 在发布模式下因意外错误不起作用:/
    【解决方案3】:

    您还需要处理来自线程的异常:

    static void Main(string[] args) {
    Application.ThreadException += MYThreadHandler;
    }
    
    private void MYThreadHandler(object sender, Threading.ThreadExceptionEventArgs e)
    {
        Console.WriteLine(e.Exception.StackTrace);
    }
    

    哎呀,抱歉,这是针对 winforms 的,对于您在控制台应用程序中使用的任何线程,您都必须将其包含在 try/catch 块中。遇到未处理异常的后台线程不会导致应用程序结束。

    【讨论】:

      【解决方案4】:

      我刚刚继承了一个旧的 VB.NET 控制台应用程序,需要设置一个全局异常处理程序。由于这个问题多次提到 VB.NET 并被标记为 VB.NET,但这里的所有其他答案都是用 C# 编写的,我想我也会为 VB.NET 应用程序添加确切的语法。

      Public Sub Main()
          REM Set up Global Unhandled Exception Handler.
          AddHandler System.AppDomain.CurrentDomain.UnhandledException, AddressOf MyUnhandledExceptionEvent
      
          REM Do other stuff
      End Sub
      
      Public Sub MyUnhandledExceptionEvent(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
          REM Log Exception here and do whatever else is needed
      End Sub
      

      我在这里使用 REM 注释标记而不是单引号,因为 Stack Overflow 似乎使用 REM 更好地处理语法突出显示。

      【讨论】:

        【解决方案5】:

        您正在尝试的内容应根据 .Net 2.0 的 MSDN 文档进行。您还可以在控制台应用程序的入口点附近的 main 中尝试 try/catch。

        static void Main(string[] args)
        {
            try
            {
                // Start Working
            }
            catch (Exception ex)
            {
                // Output/Log Exception
            }
            finally
            {
                // Clean Up If Needed
            }
        }
        

        现在您的 catch 将处理任何未捕获的内容(在主线程中)。它可以是优雅的,甚至可以根据需要重新启动,或者您可以让应用程序死掉并记录异常。如果您想进行任何清理,您将添加 finally。 每个线程都需要自己的高级异常处理,类似于主线程。

        已编辑以澄清 BlueMonkMN 指出的关于线程的观点,并在他的回答中详细显示。

        【讨论】:

        • 异常实际上仍然可以在 Main() 块之外抛出,不幸的是。这实际上并不是您想象的“包罗万象”。请参阅@Hans 的回答。
        • @Mike 首先我说他的做法是正确的,他可以尝试主要的 try/catch。我不知道为什么你(或其他人)在我同意汉斯的观点时投了反对票,只是提供了另一个我没想到会得到支票的答案。这并不公平,然后在没有提供任何证据证明 AppDomain UnhandledException 进程如何捕获 Main 中的 try/catch 无法捕获的异常的情况下说替代方案是错误的。我觉得在不证明为什么错的情况下说某事是错误的很粗鲁,只是说它是这样,但事实并非如此。
        • 我已经发布了您要求的示例。如果您没有,请负责并从迈克的旧答案中删除您不相关的反对票。 (没有个人兴趣,只是不喜欢看到这种系统滥用。)
        • 然而你仍然在玩他所做的同样的“游戏”,只是以更糟糕的方式,因为它纯粹是报复,而不是基于答案的质量。这不是解决问题的方法,只会让事情变得更糟。当您对甚至对您的回答有正当顾虑的人进行报复时(正如我所证明的那样),这尤其糟糕。
        • 哦,我还要补充一点,拒绝投票并不是针对“完全白痴或违反规则”的人,而是为了判断答案的质量。在我看来,为了“评论”提供答案的人而对答案投反对票比根据答案本身的内容对答案投反对票要大得多,无论该投票是否准确。不要采取/让它如此私人化。
        猜你喜欢
        • 1970-01-01
        • 2016-04-08
        • 1970-01-01
        • 2012-06-05
        • 2020-01-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多