Exception raised by Timer.Elapsed not caught by at

2019-08-05 02:52发布

问题:

This issue has been simplified as much as possible in attempts to isolate the problem. I hypothesize that it is related to exceptions raised in a separate thread

Let there be a WindowsFormApp.

Suppose the Program class source looks like this

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionOccurred);
        Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(UnhandledExceptionOccurred);
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    public static void UnhandledExceptionOccurred(object sender, UnhandledExceptionEventArgs e)
    {
        var x = (Exception)e.ExceptionObject;
        MessageBox.Show("Unhandled Exception\r\n" + x.Message);

    }

    public static void UnhandledExceptionOccurred(object sender, ThreadExceptionEventArgs e_thread)
    {
        var x = e_thread.Exception;
        MessageBox.Show("Thread Exception\r\n" + x.Message);
    }
}

Sorry if this is already overwhelming. Let me attempt to simplify for those happening upon this post for the accumulation of knowledge.

The static method UnhandledExceptionOccurred() has one override. These methods are used as event handlers for Unhandled Thread Exceptions, and Unhandled Exceptions coming from the main thread. (I would like to be corrected on this if this it not correct, but this is how I understand it).

Suppose in the main form, when a button is clicked an exception is thrown

private void button1_Click(object sender, EventArgs e)
{
    throw new Exception("Exception 1");
}

Suppose the button is clicked. The Exception is caught just fine with the message box reading

"Thread Exception Exception 1"

Now (almost done) suppose in the Constructor of Form1, I create a System.Timers.Timer, having an interval of 5000 miliseconds, with an event handler for it's elapsed event which throws an exception.

System.Timers.Timer T;
public Form1()
{
    InitializeComponent();
    T = new System.Timers.Timer();
    T.Interval = 5000; //Miliseconds;
    T.Elapsed += new ElapsedEventHandler(TimerElapsed);
    T.Start();
}

And the event handler:

public void TimerElapsed(object sender, ElapsedEventArgs e)
{
    T.Stop();
    throw new Exception("Exception From Timer Elapsed");
}

Now suppose the form is started, and 5 seconds pass.

No messagebox appears, and no WindowsFormError box pops up. The program silently swallows the exception, and I am able to click the button mentioned before and cause a messagebox to appear as before.

What is going on here?

I know that this is likely related to a threading thing going on with the timer. I have searched and searched and have yet to find a way to access the internals of the thread that the timer is using so that I can add some event handlers for its UnhandledException and ThreadException events.

Perhaps I could use a System.Threading.Timers Timer.

I noticed that the constructor for this type of timer takes a CallbackMethod? (What is a callback method?) Perhaps this callback method could be used to route back unhandled exceptions to the MainUI thread?

Any/All input is appreciated.

回答1:

Setting the System.Timers.Timer SyncronyzingObject to the main form fixes this issue.

System.Timers.Timer T;
public Form1()
{
    InitializeComponent();
    T = new System.Timers.Timer();
    T.SynchronizingObject = this;
    T.Interval = 5000; //Miliseconds;
    T.Elapsed += new ElapsedEventHandler(TimerElapsed);
    T.Start();
}

When the Elapsed event is handled by a visual Windows Forms component, such as a button, accessing the component through the system-thread pool might result in an exception or just might not work. Avoid this effect by setting SynchronizingObject to a Windows Forms component, which causes the method that handles the Elapsed event to be called on the same thread that the component was created on. -- Microsoft.Com