【问题标题】:Preventing Unhandled Exception Dialog Appearing防止出现未处理的异常对话框
【发布时间】:2015-01-16 21:06:41
【问题描述】:

首先让我说我已经彻底阅读了this useful article,并且正在使用 CodeProject 中的 SafeThread 类。无论使用 Thread 还是 SafeThread,我都会得到相同的结果。

我已将我的问题简化为一个包含两个表单的应用程序,每个表单都有一个按钮。主程序显示一个表格。当您单击该按钮时,会启动一个新线程,该线程会显示第二个表单。当您单击第二个表单上的按钮时,在内部它只是“抛出新的异常()”

当我在 VS2008 下运行它时,我看到“DoRun() 中的异常”。

当我在 VS2008 之外运行时,我得到一个对话框“您的应用程序中发生了未处理的异常。如果您单击继续,应用程序...。”

我已尝试将 app.config 中的 legacyUnhandledExceptionPolicy 设置为 1 和 0。

当不在 VS2008 下运行时,我需要做什么来捕获在我的第二种形式中引发的异常?

这是我的 Program.cs

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.ThreadException += new ThreadExceptionEventHandler    (Application_ThreadException);
            Application.SetUnhandledExceptionMode    (UnhandledExceptionMode.CatchException);
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            try
            {
               Application.Run(new Form1());
            }
            catch(Exception ex)
            {
                MessageBox.Show("Main exception");
            }                
        }

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            MessageBox.Show("CurrentDomain_UnhandledException");
        }

        static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
        {
            MessageBox.Show("Application_ThreadException");
        }
    }

这是 Form1:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        SafeThread t = new SafeThread(new SimpleDelegate(ThreadMain));
        try
        {
            t.ShouldReportThreadAbort = true;
            t.ThreadException += new ThreadThrewExceptionHandler(t_ThreadException);
            t.ThreadCompleted += new ThreadCompletedHandler(t_ThreadCompleted);
            t.Start();
        }
        catch(Exception ex)
        {
            MessageBox.Show(string.Format("Caught externally! {0}", ex.Message));

        }
    }

    void t_ThreadCompleted(SafeThread thrd, bool hadException, Exception ex)
    {
        MessageBox.Show("t_ThreadCompleted");
    }

    void t_ThreadException(SafeThread thrd, Exception ex)
    {
        MessageBox.Show(string.Format("Caught in safe thread! {0}", ex.Message));
    }

    void ThreadMain()
    {
        try
        {
            DoRun();
        }
        catch (Exception ex)
        {
            MessageBox.Show(string.Format("Caught! {0}", ex.Message));
        }
    }

    private void DoRun()
    {
        try
        {
            Form2 f = new Form2();
            f.Show();
            while (!f.IsClosed)
            {
                Thread.Sleep(1);
                Application.DoEvents();
            }
        }
        catch(Exception ex)
        {
            MessageBox.Show("Exception in DoRun()");
        }
    }
}

这是Form2:

public partial class Form2 : Form
{
    public bool IsClosed { get; private set; }

    public Form2()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        throw new Exception("INTERNAL EXCEPTION");
    }

    protected override void OnClosed(EventArgs e)
    {
        IsClosed = true;
    }
}

【问题讨论】:

  • 你到底想要什么样的行为?
  • 异常中的堆栈跟踪是什么?
  • 我希望我的异常处理程序之一捕获在 form2 中 button1_Click 中抛出的异常;这发生在 VS2008 中,但不在外部 这是堆栈跟踪的顶部 Trapper.Form2.button1_Click(Object sender, EventArgs e) in C:\codefarm\Trapper\Form2.cs:system.Windows.Forms.Control 的第 17 行。 System.Windows.Forms.Button.OnClick(EventArgs e) 处的 OnClick(EventArgs e)

标签: c# .net exception-handling


【解决方案1】:

如果您真的希望在单独的 UI 线程(而不是 ShowDialog())上打开第二个表单以捕获异常并将其发送到您的 Application_ThreadException 方法,则需要确保第二个线程也设置为 @ 987654324@ 你也需要订阅Application.ThreadException 那个线程。这两个都是线程特定的(有点古怪)。

可以通过调用设置默认的“未处理异常模式”:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);

这会将您创建的任何 UI 线程的应用程序范围模式设置为 CatchException。 (但在 Visual Studio 调试器和其他一些情况下运行时,此调用会失败。)或者,您的新 UI 线程可以使用通常的调用设置自己的模式(与将 true 传递给此重载相同)。

无论哪种方式,新的 UI 线程也需要订阅 Application.ThreadException 事件本身,因为订阅者存储在 [ThreadStatic] 变量中。

Application.ThreadException += Program.Application_ThreadException;

或者它可以使用单独的处理程序而不是路由到同一个处理程序,如果这有帮助的话。

我不确定这如何与使用SafeThread 来完成它相交,但我认为如果这对第二个 UI 线程正确完成,则不需要使用SafeThread。这很像您在主 UI 线程上执行的操作。

另请参阅我对this question 的回答,了解更多关于这些东西的怪癖。

【讨论】:

  • Application.ThreadException += Program.Application_ThreadException;事件处理程序运行良好,我在 Program.cs 中放置了一个事件处理程序,当发生未处理的异常时它会触发,谢谢老兄!!!
【解决方案2】:

1.) 我建议使用 BackgroundWorker 而不是这样的单独线程。您的工作人员将捕获异常并将它们作为参数传递给完整的处理程序。

2.) 在显示第二种表单时,我会使用 ShowDialog() 而不是 Show(),这将在该方法调用处阻塞 DoRun(),然后异常应该被周围的 try / catch(或 BackgroundWorker)捕获如果您改用它)。

我认为问题在于,由于您正在调用 Show(),因此您实际上是将该调用分派到 Invoker 上,最终在 UI 线程中排队。因此,当发生异常时,调用堆栈上没有任何东西可以捕获它。我相信调用 ShowDialog() 可以解决这个问题(还可以让你放弃那个讨厌的 for 循环)。

类似这样的:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        // NOTE: I forget the event / method names, these are probably a little wrong.
        BackgroundWorker worker = new BackgroundWorker();
        worker.DoWork += (o, e) =>
        {
            Form2 f = new Form2();
            e.Result = f.ShowDialog();
        };
        worker.DoWorkComplete += (o, e) =>
        { 
            if(e.Error != null)
                MessageBox.Show(string.Format("Caught Error: {0}", ex.Message));

            // else success!
            // use e.Result to figure out the dialog closed result.
        };

        worker.DoWorkAsync();
    }
}

实际上,现在我想起来了,从后台线程打开对话框有点奇怪,但我认为这仍然有效。

【讨论】:

  • 是的,从应用程序的主线程以外的线程打开对话框不仅奇怪,而且很危险。这就像从非 UI 线程更改 UI。它可以并且会在运行时对您造成影响。如果您需要基于另一个线程中的某些内容显示对话框,则需要将该线程中的事件发送回 UI 线程,然后 UI 线程才能显示该对话框。
  • 当我说“将事件发送回 UI 线程”时,我的意思是工作线程应该以某种方式导致 UI 线程的 Invoke 或 BeginInvoke 方法上的表单或控件叫做。你想怎么做(我使用一个事件,然后表单有一个方法,在直接调用显示对话框的方法或调用该方法调用 Invoke/BeginInvoke 之前检查 InvokeRequired)由你决定。
【解决方案3】:

代替这一行:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

你需要这个:

#if DEBUG
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
#else
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
#endif

这样,当您在调试模式下在 Visual Studio 中运行程序时,Visual Studio 将在异常发生时捕获异常,以便您可以在它们发生时进行调试。当您在发布模式下运行程序时,异常将被Application.ThreadException 的处理程序或AppDomain 的处理程序捕获。

这在我的程序中完美运行。我已经厌倦了收到带有“您的应用程序中发生未处理的异常...”框的电子邮件,因此我实现了一个带有文本框的通用表单,它允许我转储用于调试问题的特定信息。

【讨论】:

  • 为什么不呢?怎么了?我有一个工作应用程序(使用普通的旧线程),如果在未捕获的线程中抛出异常),全局处理程序会接受它。
  • 这两行有没有倒过来?您的文字表明您打算让发布模式使用 Application.ThreadException,这需要 CatchException 模式(仅适用于 UI 线程)。如果您将模式设置为 ThrowException(如果这是您想要的),调试器将在未处理的异常上停止。您可能还想关闭 Debugger.IsAttached 而不是 #if DEBUG。或者,您可以只使用 CatchException 并在 Visual Studio 中:Debug->Exceptions... 对话框选中该框以在抛出 CLR 异常(等)时中断。
【解决方案4】:

您是否尝试过在 main 方法中放置一个 try/catch 块?

【讨论】:

  • 是的,没有区别(更新了代码清单以反映这一点)
【解决方案5】:

问题似乎是由按钮事件处理程序引起的。如果您在 DoRun() 中抛出异常 - 可能在 Form.Show() 之后 - 无论您是否在 Visual Studio 中运行,异常都会被捕获。

有趣的是,行为取决于调试器是否附加到进程。从 Visual Studio 外部开始并稍后附加调试器会阻止发送反馈消息框,分离会使其再次出现。在 Visual Studio 中也是如此——“不调试就开始”会导致发送反馈消息框出现。

因此,在按钮事件处理程序中发生异常后,我快速浏览了框架源代码,并对其进行了粗略的查看 - 消息泵、控件以及可能很多其他代码在那里做了很多事情。因为 WinForms 只是本机控件的包装器,所以我假设由于某种原因,异常不会返回到同一点或线程,具体取决于是否附加了调试器 - 当异常通过线程或进程边界传递时可能会出现问题或类似的东西。

【讨论】:

  • 这里发生的事情是UnhandledExceptionMode.CatchException 允许任何东西在代码运行时捕获异常,从而中断您设置的链。虽然附加了 VS,但它恰好是 Visual Studio 本身。如果不是这样,.NET Framework 会首先处理该异常,并通过弹出那个烦人的无用对话框来实现。通过将其设置为 UnhandledExceptionMode.ThrowException,异常会继续冒泡到您的应用程序线程异常或 AppDomain 处理程序,正如您所期望的那样。
  • UnhandledExceptionMode.CatchException 告诉消息循环捕获处理消息时发生的异常并将它们路由到 Application.ThreadException(如果您不订阅,WinForms 会自行处理)。如果它们没有在那里被捕获,它们可以展开为发送 AppDomain.UnhandledException 并卸载 AppDomain 的未处理异常。但是附加的调试器可以拦截它并恢复原始调用堆栈(?),并可能允许您围绕它进行调试(如果您可以通过 SetNextStatement 避免它)。如果没有调试器,您会在 UE 事件返回后获得死亡对话框。
猜你喜欢
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-01
  • 1970-01-01
相关资源
最近更新 更多