views:

87

answers:

3

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 ?

A: 

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

    ...
}
Jesse C. Slicer
Changed answer.
Jesse C. Slicer
Your suggestion makes sense, but this doesn't change the strange behaviour.
Brann
@Brann, you are correct. The strange behaviour persists. However, I have corrected it by not doing `catch (Exception ex)` in the button click handler, but rather just `catch`. Very interesting. An exception not decendand of Exception being thrown.
Jesse C. Slicer
A: 

You have to call

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

first in your Main() method.

codymanix
I've tried that, without success.
Brann
+4  A: 

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.

Hans Passant
exactly the answer I was looking for ; thanks.
Brann
The `GetBaseException` call is designed to unwrap `TargetInvocationException` s from `Invoke`.
SLaks
That's not it. Control.BeginInvoke() is not at all the same as Delegate.BeginInvoke(). It doesn't marshal the exception back to the caller, for one.
Hans Passant