Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Very strange Application.ThreadException behaviour

I'm using the Application.ThreadException event to handle and log unexpected exceptions in my winforms application.

Now, somewhere in my application, I've got the following code (or rather something equivalent, but this dummy code is enough to reproduce my issue) :

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

I'm clearly expecting my Application_ThreadException handler to be passed the "test2" exception, but this is not always the case. Typically, if another thread marshals my code to the UI, my handler receives the "test" exception, exactly as if I hadn't caught "test" at all.

Here is a short sample reproducing this behavior. I have omitted the designer's code.

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

The output of this program on my computer is :

test
... here I click button1
test2

I've reproduced this on .net 2.0,3.5 and 4.0. Does someone have a logical explanation ?

like image 968
Brann Avatar asked Jun 09 '10 15:06

Brann


2 Answers

There's a bug in your code that makes it hard to debug what's going on: you start the thread before the form's Handle is created. That will make BeginInvoke fail. Fix:

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

Anyhoo, this is designed behavior. The Windows Forms code that runs the BeginInvoke target looks like this:

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

It is the exception.GetBaseException() call that screws up your exception message. Why the Windows Forms designers chose to do this isn't quite clear to me, there is no comment with the code in the Reference Source. I can only guess that without it the exception would be more difficult to debug, in case it is raised by the Windows Forms plumbing code instead of the application code. Not a great explanation.

They have already said that they won't fix it, perhaps you could add your vote. Don't get your hopes up.

The workaround is to not set the InnerException. Not a great option of course.

like image 174
Hans Passant Avatar answered Oct 13 '22 01:10

Hans Passant


Exception #1: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

So, don't attempt to call from the constructor. Do it in 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();
    }

    ...
}
like image 39
Jesse C. Slicer Avatar answered Oct 12 '22 23:10

Jesse C. Slicer