异常处理循环拼图

Joh*_*dol 15 .net c# exception-handling exception

我最近遇到过一种我以前从未见过的行为.由于缺乏关于内部工作异常处理的基础知识,或者我只是遗漏了一些明显的东西,我无法理解最有可能发生的事情.

我最近在应用程序中添加了异常处理,作为未处理异常情况下的一种后备.我基本上处理ThreadException和UnhandledException,如下所示:

// Add the event handler for handling UI thread exceptions to the event.
Application.ThreadException += new ThreadExceptionEventHandler(ExceptionHandler.OnUIThreadException);

// Set the unhandled exception mode to force all Windows Forms errors to go through
// our handler.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

// Add the event handler for handling non-UI thread exceptions to the event. 
AppDomain.CurrentDomain.UnhandledException +=
    new UnhandledExceptionEventHandler(ExceptionHandler.OnUnhandledException);

// Runs the application.
Application.Run(new ErrorHandlerForm());
Run Code Online (Sandbox Code Playgroud)

我在应用程序中使用的其他一些代码已经捕获了异常 - 因为我没有进行异常处理,所以我只是重新抛出异常以确保它没有被吞下:

//code in some method of the Program
try
{
   foo.SomeFooCall();
}
catch(Exception ex)
{
  logger.Log(ex.Message);
  // I don't swallow!
  throw;
}
Run Code Online (Sandbox Code Playgroud)

一旦我进行了异常处理(也是记录),我应该删除上面的try catch块 - 但我忘了这样做,我遇到了一个奇怪的行为,这是这个问题的主题.

当在foo调用中的某处抛出异常时,它显然被上面的代码捕获,然后再次被抛出.此时ExceptionHandling启动,然后进行一些日志记录和通知(一个简单的消息框)Application.Exit().接下来发生的事情是应用程序将返回相同的位置throw,这将触发相同结果的错误处理,并且这将持续多次,直到它崩溃可能是因为堆栈跟踪已满或者它以某种方式检测到无限循环.

编辑:以上是在调试模式 - 如果我只是运行它它将处理一次异常(显示消息框,日志等),然后它只会崩溃(我猜测堆栈溢出).

我预计这个问题的答案可能微不足道(或者我可能会遗漏一些明显的东西) - 但任何指针/解释都会受到高度赞赏.

编辑: 异常处理程序方法调用两个OnException方法,如下所示:

private void OnUIThreadException(object sender, ThreadExceptionEventArgs e)
{
   OnException(e.Exception);
}

private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
   OnException((Exception)e.ExceptionObject);
}

private void OnException(Exception exception)
{
   MessageBox.Show("Fatal Exception: " + exception.Message);

   logger.Log(LoggingLevel.FATAL, "myLousyApp", exception.Message);

   Application.Exit();
}
Run Code Online (Sandbox Code Playgroud)

我实际上做的不仅仅是smt - 例如询问用户是否要重新启动应用程序,如果是这样,请将进程ID重新启动为cmd arg,以便在重新启动时等待旧进程退出(它是通过互斥锁保护免受重复实例的影响.但是对于这个问题,这是无关紧要的,因为当我遇到所描述的行为时,我没有重新启动应用程序.

编辑:我创建了另一个简单的应用程序来重现这个条件 - 我有一个简单的组件,抛出异常(我在循环中抛出任意数量的异常),但在我对Application.Exit的所有测试中,应用程序只是很好地关闭和我无法重现它.对于我应该寻找的东西感到困惑!

Tul*_*x86 13

tl; dr:这是调试器.分离,你不会得到这种奇怪的行为.


好的.我做了一个品牌Spankin'新项目的实验,并得出了一个结果.我将首先发布代码,以便您也可以加入其中,直接看到它.

Teh Codez

请将它们发送给我(无关)

Form1.cs的

示例表格
表格上需要两个按钮.适当地标注它们,以便明显地表达你正在做的事情.

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

    private void button1_Click(object sender, EventArgs e)
    {
        throw new InvalidOperationException("Exception thrown from UI thread");
    }

    private void button2_Click(object sender, EventArgs e)
    {
        new Thread(new ThreadStart(ThrowThreadStart)).Start();
    }

    private static void ThrowThreadStart()
    {
        throw new InvalidOperationException("Exception thrown from background thread");
    }
}
Run Code Online (Sandbox Code Playgroud)

Program.cs中

static class Program
{
    [STAThread]
    static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        Application.ThreadException += Application_ThreadException;
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        if (e.Exception != null)
        {
            MessageBox.Show(string.Format("+++ Application.ThreadException: {0}", e.Exception.Message));
        }
        else
        {
            MessageBox.Show("Thread exception event fired, but object was not an exception");
        }
    }

    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Exception ex = e.ExceptionObject as Exception;

        if (ex != null)
        {
            MessageBox.Show(string.Format("*** AppDomain.UnhandledException: {0}", ex.Message));
        }
        else
        {
            MessageBox.Show("Unhandled exception event fired, but object was not an exception");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

项目文件

禁用托管进程,否则AppDomain(和Forms本身)将不会在调试会话之间卸载,如果您在运行之间更改参数,则会使该行Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);抛出.编辑:或者,如果设置为CatchExceptionInvalidOperationExceptionUnhandledExceptionMode

这个调查并不是绝对必要的,但是如果你打算四处游戏并改变UnhandledExceptionMode- 我希望你自己可能会自己运行这个代码 -这个设置可以让你心痛. 禁用托管过程

测试

在调试器里面

扔进UI线程

  1. 单击" 在UI中投掷"
  2. 在调试器中获取"未处理的异常"帮助器
  3. F5 继续执行
  4. 将显示对话框,指示应用程序处理程序从UI线程收到异常事件
  5. 单击确定
  6. 应用不会崩溃,所以请随意冲洗并重复.

扔进后台线程

  1. 点击后台投掷
  2. 将显示对话框,指示AppDomain处理程序从后台线程收到异常事件
  3. 在调试器中获取"未处理的异常"帮助器
  4. F5 继续执行
  5. goto 2.真.

看来AppDomain处理程序无论出于何种原因都胜过调试器.然而,在完成AppDomain之后,调试器确实设法接收未处理的异常.但接下来发生的事情令人费解:AppDomain处理程序再次运行.然后再次.再一次,无限广告.在处理程序中放置一个断点表明这不是递归的(至少不在.NET中),所以这可能不会在堆栈溢出中结束. 后台线程

现在,我们来试试吧......

在调试器外面

UI线程

相同的过程,相同的结果 - 当然除了显然没有调试器助手.程序仍然没有崩溃,因为UnhandledExceptionMode.CatchException它会说它会做什么,并且"内部"处理异常(至少在Forms中),而不是将其升级到Feds AppDomain.

背景线程

现在这很有趣.

  1. 点击后台投掷
  2. 将显示对话框,指示AppDomain处理程序从后台线程收到异常事件
  3. 获取Windows崩溃对话框
    Windows崩溃对话框
  4. 单击Debug以使用JIT调试来抢夺异常
  5. 得到"未处理的异常"助手
  6. F5 接着说
  7. 程序退出

首先,AppDomain不会像附加调试器一样进入循环,其次,从Windows错误对话框及时附加调试器不会触发这种奇怪的行为.

结论

似乎调试器对未处理的异常进入AppDomain做了一些奇怪的事情.我不太了解调试器如何发挥其魔力,所以我不会推测,但循环发生在连接调试器时,所以如果循环是问题,分离是你可以使用的一种解决方法(也许使用Debugger.Launch()如果你需要的话,给自己时间重新安装一下).

<3


Jon*_*nno 7

它只是一个默认启用的VS选项(仅在调试时才相关).因此,如果你想要的只是(测试?)崩溃和烧录你未调试的进程将不需要分离调试器.

这种行为受到控制的地方

  • 这是正确的答案; 启用此选项时,调试器会自动展开堆栈并将当前语句设置为_before_ throw,这会导致Run(resume)再次抛出异常.请注意,你也可以根据具体情况解决这个问题,只需使用'set next statement'来跳过throw,并允许你的后退错误处理按照它的方式展开. (4认同)