C# WinForm中的静态void Main方法无法捕获异常吗?

7

我有一个使用C#编写的WinForm应用程序,在程序入口Program.cs中添加了try-catch块,即在应用程序的static void Main方法的开头添加如下代码:

using System;
using System.IO;
using System.Windows.Forms;

namespace T5ShortestTime {
    static class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() {
            try {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new T5ShortestTimeForm());
            } catch (Exception e) {
                string errordir = Path.Combine(Application.StartupPath, "errorlog");
                string errorlog = Path.Combine(errordir, DateTime.Now.ToString("yyyyMMdd_HHmmss_fff") + ".txt");
                if (!Directory.Exists(errordir))
                    Directory.CreateDirectory(errordir);                
                File.WriteAllText(errorlog, e.ToString());              
            }
        }
    }
}

正如您所看到的,Application被放置在一个try-catch块中,在catch块中,它唯一要做的就是创建一个错误日志文件。
现在,到目前为止还不错。我的应用程序运行良好,如果遇到崩溃,最后一个Exception应该被try-catch块捕获并存储在错误日志文件中。
然而,当我运行程序一段时间后,我会遇到一个未处理的异常(null引用)。令我惊讶的是,这个异常没有创建错误日志文件。
现在,这篇文章表明可能是由于ThreadExceptionHandleProcessCorruptedStateExceptions(最受欢迎的两个答案)引起的,但我的情况只是一个简单的null引用异常:
Problem signature:
  Problem Event Name:   CLR20r3
  Problem Signature 01: T5ShortestTime.exe
  Problem Signature 02: 2.8.3.1
  Problem Signature 03: 5743e646
  Problem Signature 04: T5ShortestTime
  Problem Signature 05: 2.8.3.1
  Problem Signature 06: 5743e646
  Problem Signature 07: 182
  Problem Signature 08: 1b
  Problem Signature 09: System.NullReferenceException
  OS Version:   6.3.9600.2.0.0.272.7
  Locale ID:    1033
  Additional Information 1: bb91
  Additional Information 2: bb91a371df830534902ec94577ebb4a3
  Additional Information 3: aba1
  Additional Information 4: aba1ed7202d796d19b974eec93d89ec2

Read our privacy statement online:
  http://go.microsoft.com/fwlink/?linkid=280262

If the online privacy statement is not available, please read our privacy statement offline:
  C:\Windows\system32\en-US\erofflps.txt

为什么会这样呢?

2
这不是创建全局异常处理程序的正确方法。请查看此页面右侧的“链接”部分。那里的被接受的答案会告诉你该怎么做。 - jmcilhinney
@jmcilhinney 你是指 ThreadException 吗? - Ian
在这个例子中,有一行注释:// Start a new thread, separate from Windows Forms, that will throw an exception.(启动一个新线程,与 Windows Forms 分离,该线程将抛出异常。)代码通过新线程故意处理异常。但是,它是否会创建 null 引用异常(就像我的情况)而不是 ThreadException(应该是这种类型吗?)? - Ian
3个回答

5

try-catch块应该捕获最后一个异常

这不会发生。除非您使用调试器运行程序,否则不会发生。因此,您肯定会被麻痹而相信它会起作用,每个人都会开始运行他们的程序一段时间。

Application.Run() 在其代码中具有后备功能,可以引发事件,当事件处理程序引发未处理的异常时,try/catch-em-all会引发Application.ThreadException事件。 那个后备功能确实非常重要,特别是在Windows 7的x64版本上。 如果没有异常处理程序,会发生非常糟糕的事情。 然而,在您使用调试器运行时,该后备功能不会启用,这使得未处理的异常过于难以调试。

因此,当您进行调试时,您的catch子句将运行。 这使得未处理的异常过于难以调试。 当您没有使用调试器运行时,您的catch子句将不会运行,您的程序将崩溃,就像您描述的那样。 这使得未处理的异常过于难以调试。

所以不要这样做。Application.Run()如何处理未处理的异常是通过Application.SetUnhandledExceptionMode()方法配置的。你会喜欢这个版本更好:
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        if (!System.Diagnostics.Debugger.IsAttached) {
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
            AppDomain.CurrentDomain.UnhandledException += LogException;
        }
        Application.Run(new Form1());
    }

    private static void LogException(object sender, UnhandledExceptionEventArgs e) {
        string errordir = Path.Combine(Application.StartupPath, "errorlog");
        string errorlog = Path.Combine(errordir, DateTime.Now.ToString("yyyyMMdd_HHmmss_fff") + ".txt");
        if (!Directory.Exists(errordir))
            Directory.CreateDirectory(errordir);
        File.WriteAllText(errorlog, e.ToString());
        AppDomain.CurrentDomain.UnhandledException -= LogException;
        MessageBox.Show("Error details recorded in " + errorlog, "Unexpected error");
        Environment.Exit(1);
    }

有了这段代码,您可以轻松调试未处理的异常。Debugger.IsAttached测试确保调试器在事件处理程序失败时总是停止。如果没有调试器,则禁用Application.ThreadException事件(它非常无用),并倾向于侦听所有异常,包括在工作线程中引发的异常。

您应该向用户发出警报,以便窗口不会消失而留下任何痕迹。我本来想推荐MessageBox,但注意到this bug目前在Windows 10上又出现了。叹气。


谢谢您的解释,这应该就是答案了。最后一个问题,您说这对于Windows 7 x64特别糟糕,而我使用的是Windows 10 x64。这有什么区别吗?在您的帖子中,您提到:“升级到Windows 8或更高版本,它们已经解决了这个wow64问题。” - 我认为这也适用于Windows 10,这正确吗? - Ian
Win10没有这个问题,这个问题非常特定于Win7。 - Hans Passant
好的,谢谢。那我就放心了... ;) 我还注意到DisposeException之后仍然被调用。我实现了SetUnhandledExceptionModeThrowException,并使用throw new System.Exception()进行了测试。令人惊讶的是,程序生成了两个错误日志文件,而不是一个。一个是System.Exception,另一个是Dispose中的Exception - 而Dispose中的那个是产生的。这是为什么呢?Dispose在致命异常之后仍然被调用,这是否符合预期? - Ian
1
不太清楚你在谈论哪个Dispose方法。可能实际上是Dispose(bool)重载,也被终结器调用。您可以选择如何终止程序,Environment.Exit()仍然通过运行终结器来清理,Environment.FailFast()则不会。当 disposing 参数为false时,Dispose(bool)不应执行任何危险操作,您永远不希望崩溃终结器线程。如果这不能解决您的问题,请单击“提问”按钮。 - Hans Passant
评论中的解释很有帮助。似乎第二个 Exception 是由于 Environment.Exit 引起的。当我没有放置 Environment.Exit,但替换为 Environment.FailFast(是的,它是由 WinForm 拥有的默认的 无参 Dispose),程序没有产生两个日志。我还预计,如果使用 AppDomain.CurrentDomain.UnhandledException -= LogException; 行,它也会做同样的事情。再次感谢!;) - Ian

2
ThreadException不像(NullReferenceException)那样是一种异常类型。它是这样的:
此事件允许您的Windows Forms应用程序处理在Windows Forms线程中发生的未处理异常。
这意味着它处理主线程以外的线程中的异常。
因此,您需要订阅:AppDomain.CurrentDomain.UnhandledException,以便处理主线程中的异常(无论异常类型如何,例如NullReferenceIndexOutOfRange等)。请注意保留HTML标签。

2
好的,最终我按照Hans Passantthis post中为VB.Net所示的例子实现了Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException)。这里我放上了我自己的代码+错误日志记录,适用于C#
static void Main() {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    if (!System.Diagnostics.Debugger.IsAttached) {
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
        AppDomain.CurrentDomain.UnhandledException += LogUnhandledExceptions;
    }
    Application.Run(new T5ShortestTimeForm());
}

private static void LogUnhandledExceptions(object sender, UnhandledExceptionEventArgs e) {
    Exception ex = (Exception)e.ExceptionObject;
    string errordir = Path.Combine(Application.StartupPath, "errorlog");
    string errorlog = Path.Combine(errordir, DateTime.Now.ToString("yyyyMMdd_HHmmss_fff") + ".txt");
    if (!Directory.Exists(errordir))
        Directory.CreateDirectory(errordir);
    File.WriteAllText(errorlog, ex.ToString());
    Environment.Exit(System.Runtime.InteropServices.Marshal.GetHRForException(ex));
}

此外,似乎造成混淆的源头是实际上会出现两个传播的异常:Exception

第一个是来自应用程序本身的任何异常:

System.Exception: Exception of type 'System.Exception' was thrown.
   at T5ShortestTime.T5ShortestTimeForm..ctor() in C:\Test.cs:line 45
   at T5ShortestTime.Program.Main() in C:\Test.cs:line 19
   at ...
第二个异常发生在Form组件的Dispose期间,它会创建另一个异常,即null引用异常。
System.NullReferenceException: Object reference not set to an instance of an object.
   at T5ShortestTime.T5ShortestTimeForm.Dispose(Boolean disposing)
   at System.ComponentModel.Component.Finalize()
因此,当我在我的应用程序中测试异常时,NullReferenceException 最后出现在 Dispose 中。

enter image description here

我只有在将上面的UnhandledExceptionMode设置为ThrowException后才能捕获到这个。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接