代码之家  ›  专栏  ›  技术社区  ›  Brann

非常奇怪的应用。线程异常行为

  •  9
  • Brann  · 技术社区  · 15 年前

    我正在使用 Application.ThreadException 事件来处理和记录WinForms应用程序中的意外异常。

    现在,在我的应用程序中的某个地方,我已经得到了以下代码(或者更确切地说是类似的代码,但这个伪代码足以重现我的问题):

                try
                {
                    throw new NullReferenceException("test");
                }
                catch (Exception ex)
                {
                    throw new Exception("test2", ex);
                }
    

    很明显,我希望我的应用程序线程异常处理程序通过“test2”异常,但情况并非总是如此。通常情况下, 如果另一个线程将我的代码封送到UI,我的处理程序将接收“测试”异常,就好像我根本没有捕获“测试”一样。

    这里是一个复制这种行为的简短示例。我省略了设计师的代码。

         static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
            //Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); // has no impact in this scenario, can be commented.
    
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
    
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    
           static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            //this handler is never called
        }
    
        static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            Console.WriteLine(e.Exception.Message);
        }
    }
    
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            button1.Click+=new EventHandler(button1_Click);
        }
    
        protected override void OnLoad(EventArgs e) {
        System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx));
        t.Start();
        }
    
    
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                throw new NullReferenceException("test");
            }
            catch (Exception ex)
            {
                throw new Exception("test2", ex);
            }
        }
    
        void ThrowEx()
        {
            this.BeginInvoke(new EventHandler(button1_Click));
        }
    }
    

    此程序在我的计算机上的输出是:

    test
    ... here I click button1
    test2
    

    我在.NET 2.0、3.5和4.0上复制了这个。有人有合理的解释吗?

    3 回复  |  直到 15 年前
        1
  •  7
  •   Hans Passant    15 年前

    您的代码中有一个bug,使您很难调试正在发生的事情:在创建表单句柄之前启动线程。这将使贝吉宁诺克失败。修复:

        protected override void OnLoad(EventArgs e) {
            System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx));
            t.Start();
        }
    

    总之,这是设计行为。运行BeginInvoke目标的Windows窗体代码如下:

      try
      {
          this.InvokeMarshaledCallback(tme);
      }
      catch (Exception exception)
      {
          tme.exception = exception.GetBaseException();
      }
      ...
           if ((!NativeWindow.WndProcShouldBeDebuggable && (tme.exception != null)) && !tme.synchronous)
           {
               Application.OnThreadException(tme.exception);
           }
    

    正是exception.getBaseException()调用导致异常消息出错。我不太清楚为什么Windows窗体设计人员选择这样做,引用源中的代码没有注释。我只能猜测,如果没有它,异常将更难调试,以防它是由Windows窗体管道代码而不是应用程序代码引发的。不是很好的解释。

    他们已经说过 won't fix it ,也许你可以增加你的投票。不要抱着希望。

    解决方法是不设置InnerException。当然不是很好的选择。

        2
  •  1
  •   Jesse C. Slicer    15 年前

    例外情况1: Invoke BeginInvoke 在创建窗口句柄之前无法对控件调用。

    所以,不要试图从构造函数调用。做进去 OnLoad() :

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.Load += new EventHandler(Form1_Load);
            button1.Click += new EventHandler(button1_Click);
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThrowEx));
            t.Start();
        }
    
        ...
    }
    
        3
  •  0
  •   codymanix    15 年前

    你得打电话

    application.setUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

    首先在main()方法中。