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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1105573/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 08:22:28  来源:igfitidea点击:

Preventing Unhandled Exception Dialog Appearing

c#.netexception-handling

提问by rc1

First let me say I have read this useful articlethoroughly and am using the SafeThread class from CodeProject. I get the same result whether using Thread or SafeThread.

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

I have reduced my problem down to an app consisting of two forms each with one button. The main program displays a form. When you click that button, a new thread starts which displays a second form. When you click the button on the second form, internally it just does "throw new Exception()"

我已将我的问题简化为由两个表单组成的应用程序,每个表单都有一个按钮。主程序显示一个表格。当您单击该按钮时,将启动一个新线程,并显示第二个表单。当您单击第二个表单上的按钮时,它在内部只是执行“throw new Exception()”

When I run this under VS2008, I see "Exception in DoRun()".

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

When I run outside of VS2008, I get a dialog box "Unhandled exception has occurred in your application. If you click continue, the application ...."

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

I have tried setting legacyUnhandledExceptionPolicy in the app.config to both 1 and 0.

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

What do I need to do to capture the exception thrown in my second form, when not running under VS2008?

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

Here's my Program.cs

这是我的 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");
        }
    }

Here's Form1:

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

And here is Form2:

这是 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;
    }
}

采纳答案by justin.m.chase

1.) I would recommend using the BackgroundWorker instead of separate threads like this. Your worker will catch exceptions and pass them along as a parameter to the complete handler.

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

2.) I would use ShowDialog() instead of Show() when displaying the second form, this will block the DoRun() at that method call and exceptions should then be caught by your surrounding try / catch (or the BackgroundWorker if you're using that instead).

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

I think the problem comes that since you're calling Show() you're essentially dispatching that call onto the Invoker, which ends up being queued in the UI thread. So when an exception happens there is nothing higher up the callstack to catch it. I believe calling ShowDialog() will fix this (and also allow you to drop that nasty for loop).

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

Something like this:

像这样的东西:

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

Actually, now that I think about it, it's sort of weird to be opening a dialog from a background thread but I think this will still work.

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

回答by Paul Sonier

Have you tried putting a try / catch block in your main method?

你有没有试过在你的主方法中放置一个 try / catch 块?

回答by Daniel Brückner

The problem seems to be caused by the button event handler. If you throw an exception in DoRun()- maybe after Form.Show()- the exception will be caught as exspected no matter if you are runing inside Visual Studio or not.

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

Interesting is, that the behavior depends on whether the debugger is attached to the process, or not. Starting outside of Visual Studio and attaching the debugger later prevents the send feedback message box, detaching makes it occur again. The same from within Visual Studio - "Start without debugging" cause the send feedback message box to occur.

有趣的是,行为取决于调试器是否附加到进程。在 Visual Studio 外部启动并稍后附加调试器会阻止发送反馈消息框,分离会使它再次发生。Visual Studio 中的情况相同 - “启动但不进行调试”会导致发送反馈消息框出现。

So I quickly steped through the framework source code after the exception occured in the button event handler and had a rough look at it - the message pump, the controls, and probably a lot of other code do a lot of stuff there. Because WinForms are just wrapper around native controls I assume that for some reason the exception is not returned to the same point or thread depending on whether a debugger is attached or not - maybe something goes wrong when the exception is passed across thread or process boundaries or something like that.

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

回答by jasonh

Instead of this line:

而不是这一行:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

You need this:

你需要这个:

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

This way, when you run the program in Visual Studio under Debug mode, Visual Studio will trap the exceptions when they happen so you can debug them at the point they occur. When you run your program in release mode, the exceptions will be caught by the handler for Application.ThreadExceptionor the handler for the AppDomain.

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

This works perfectly in my program. I got tired of getting emails with the "Unhandled exception has occurred in your application..." box, so I implemented a universal form with a text box that allows me to dump specific information that I use to debug the problem.

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

回答by Rob Parker

If you really want your second Form opened on a separate UI thread (not as ShowDialog()) to catch the exception and send it to your Application_ThreadExceptionmethod, you need to ensure that the second thread is also set to CatchExceptionandyou need to subscribe to the Application.ThreadExceptionon that thread, too. Both of these are thread-specific (and a bit quirky).

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

You can set the default "unhandled exception mode" by calling:

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

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);

This sets the application-wide mode to CatchExceptionfor any UI thread you create. (But this call will fail when running in the Visual Studio debugger and some other cases.) Or, your new UI thread can set its own mode with the usual call (same as passing true to this overload).

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

Either way, the new UI thread also needs to subscribe to the Application.ThreadExceptionevent itself, because the subscriber is stored in a [ThreadStatic] variable.

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

Application.ThreadException += Program.Application_ThreadException;

Or it could use a separate handler instead of routing to the same one, if that's helpful.

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

I'm not sure how this might intersect with using SafeThreadto accomplish it, but I think if this is done correctly for the second UI thread it wouldn't be necessary to use SafeThread. It's much like you'd do it on your main UI thread.

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

Also see my answer to this questionfor more on the quirks of this stuff.

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