防止出现未处理的异常对话框

时间:2009-07-09 18:05:19

标签: c# .net exception-handling

首先让我说我已经彻底阅读了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;
    }
}

5 个答案:

答案 0 :(得分:3)

如果您确实希望在单独的UI线程(而不是ShowDialog())上打开第二个表单来捕获异常并将其发送到您的Application_ThreadException方法,则需要确保第二个线程是也设置为CatchException ,您还需要订阅该线程上的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的回答,了解更多关于此事的怪癖。

答案 1 :(得分:2)

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

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

我认为问题出现了,因为你正在调用Show(),你实际上是将调用调用到Invoker,最终在UI线程中排队。因此,当异常发生时,callstack没有任何东西可以捕获它。我相信调用ShowDialog()会解决这个问题(也允许你放弃那个讨厌的循环)。

这样的事情:

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

实际上,现在我考虑一下,从后台线程打开一个对话框有点奇怪,但我认为这仍然有用。

答案 2 :(得分:1)

而不是这一行:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

你需要这个:

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

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

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

答案 3 :(得分:0)

您是否尝试在主方法中添加try / catch块?

答案 4 :(得分:0)

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

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

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