控制台应用程序中的 .NET 全局异常处理程序

.NET Global exception handler in console application

提问人:Stefan Steiger 提问时间:6/28/2010 最后编辑:spinodalStefan Steiger 更新时间:6/20/2020 访问量:117915

问:

问:我想在控制台应用程序中为未处理的异常定义全局异常处理程序。在 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 异常 控制台应用程序

评论


答:

313赞 Hans Passant 6/28/2010 #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)] 属性应用于它。

评论

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

您正在尝试的操作应根据 .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
    }
}

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

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

评论

1赞 Mike Atlas 6/29/2010
不幸的是,异常实际上仍然可以抛出 Main() 块之外。这实际上并不像您想象的那样“包罗万象”。请看@Hans的回答。
0赞 Rodney S. Foley 6/29/2010
@Mike 首先,我说他这样做的方式是正确的,他可以尝试尝试/捕捉。我不确定为什么你(或其他人)在我同意汉斯只是提供另一个我没想到会得到支票的答案时给我投了反对票。这并不公平,然后说替代方案是错误的,而没有提供任何证据来证明 AppDomain UnhandledException 进程如何捕获 Main 中的 try/catch 无法捕获的异常。我发现在不证明为什么是错的情况下说某件事是不礼貌的,只是说它是错的,并不能使它成为这样。
6赞 BlueMonkMN 6/29/2010
我已经发布了您要求的示例。请负责任,如果你没有,请从迈克的旧答案中删除你不相关的反对票。(没有个人兴趣,只是不喜欢看到这种滥用系统。
3赞 BlueMonkMN 6/29/2010
然而,你仍然在玩和他一样的“游戏”,只是以更糟糕的方式,因为这是纯粹的报复,而不是基于答案的质量。这不是解决问题的方法,只会让问题变得更糟。当你报复一个甚至对你的答案有合理担忧的人时,这尤其糟糕(正如我所证明的那样)。
3赞 BlueMonkMN 6/29/2010
哦,我还要补充一点,反对票不是为那些“彻头彻尾的白痴或违反规则”的人准备的,而是为了判断答案的质量。在我看来,为了“评论”提供答案的人而对答案投反对票比根据答案本身的内容投反对票的答案要大得多,无论该投票是否准确。不要把它看得那么个人化。
12赞 BlackICE 6/28/2010 #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 块中。遇到未经处理的异常的后台线程不会导致应用程序结束。

25赞 BlueMonkMN 6/29/2010 #4

如果您有一个单线程应用程序,则可以在 Main 函数中使用简单的 try/catch,但是,这并不涵盖可能在 Main 函数之外抛出的异常,例如在其他线程上(如其他注释中所述)。此代码演示了异常如何导致应用程序终止,即使您尝试在 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");
   }
}

评论

0赞 Muflix 7/1/2015
TRY CATCH 在发布模式下因意外错误而不起作用:/
0赞 Chris Kuliukas 9/15/2022
哎呀,这当然是鼓励真的很糟糕。迟早,你的应用程序会在线程中引入一个错误,它会在中途停止,然后静默地退出,你会认为它完全工作,你会花很长时间试图弄清楚它,然后才想起你愚蠢地抑制了所有异常。
2赞 BlueMonkMN 11/10/2022
@ChrisKuliukas 不会禁止显示异常。它们正在输出到控制台。与应用程序退出而不处理异常相比,您更有可能以这种方式看到它。
1赞 Russell Jonakin 6/20/2020 #5

我刚刚继承了一个旧的 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

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