views:

880

answers:

4

I've encountered a strange difference between a program running in VS2005 and running the executable directly. Essentially, when an exception is thrown in a method inside an Application.DoEvents() call, the exception can be caught when running inside Visual Studio. When running the compiled executable, the exception is not caught and the program crashes.

Here is some simple code to demonstrate the problem. Assume standard winforms boilerplate and two buttons and a label.

To run this, click the start button to start the 10 second count. Before 10 seconds elapses, press the abort button. and an exception will be thrown inside the DoEvents(). The exception should be caught. This only happens when running inside Visual Studio.

    private void StartButton_Click(object sender, EventArgs e) {
        DateTime start = DateTime.Now;

        try {
            while (DateTime.Now - start < new TimeSpan(0, 0, 10)) {
                this.StatusLabel.Text = DateTime.Now.ToLongTimeString();
                Application.DoEvents();
            }

            MessageBox.Show("Completed with no interuption.");
        } catch (Exception) {
            MessageBox.Show("User aborted.");                
        }
    }

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

I want to be able to catch these exceptions. Is there any way to make it work?

Update:

I'm willing to consider approaches other than the re-entrant-headache-inducing DoEvents(). But I haven't found one that seems to work better. My scenario is that I have a long running loop which is controlling some scientific instruments, and frequently has to wait for a temperature to stabilize or something. I want to give my users the ability to abort the process, so I have an abort button that simply throws a custom exception, that I intend to catch at the site where the process is originally kicked off. It seemed to be a perfect solution. Except for the fact that it doesn't work for some reason.

If it's not possible to get this to work, is there a better approach?

Update 2:

When I add this as the first line of Main(), that makes it work as an executable, but not in VS, so the situation is reversed. The crazy thing is that it appears to be a no-op. I can understand how this does anything.

Application.ThreadException += delegate(
        object sender, 
        System.Threading.ThreadExceptionEventArgs e
    ) 
    { throw e.Exception; };

This is insane.

+4  A: 

Do you really have to use DoEvents? It leads to re-entrancy which can be very hard to debug.

I suspect that if you remove the re-entrancy from your application, you'll be able to work out where to catch the exception more easily.

EDIT: Yes, there's definitely a better way of doing this. Do your long-running task on a different thread. The UI thread should only be doing UI operations. Make your "abort" button set a flag which the long-running task checks regularly. See my WinForms threading page for an example of WinForms threading, and the volatility page for an example of one thread setting a flag to be watched by another thread.

EDIT: I've just remembered that BackgroundWorker (which my article doesn't cover - it was written pre-.NET 2.0) has a CancelAsync method - basically this (used with the CancellationPending and WorkerSupportsCancellation which basically deals with the "have a flag to set to cancel" stuff for you. Just check CancellationPending from the worker thread, and call CancelAsync from your abort button click handler.

Jon Skeet
I did it this way intentionally, hoping to provide my users with an abort button that kills a long running process, which spends most of its time waiting for external instruments. What's the best way of doing this without DoEvents? (see also, new update in OP)
recursive
Damn it, Skeet. I was carefully crafting my tiny answer while you edited this.
Jeff Yates
I've always heard that you should always avoid threads if you possibly can because debugging them is so hard. The wording of your edit suggests that all apps that have non-UI operations (all apps) should be multi-threaded. I'm confused.
recursive
Avoiding threading because debugging is hard is really not a valid excuse. You need to use the tools appropriate to the task and in this case, it is appropriate. Debugging them is not that hard - synchronisation of threads can be hard to debug but not in this case, I expect.
Jeff Yates
And trying to deal with re-entrancy is even harder, IMO.
Jon Skeet
Yes, that too. :)
Jeff Yates
+2  A: 

Because DoEvents causes the underlying message loop to pump, it can lead to re-entrancy (as stated in MSDN) which will make the behaviour of your application unpredictable.

I would suggest that you move your work (waiting for a temperature to stabilize) into a thread and use a signal of some kind to tell your user interface when the wait is over. This will allow your user interface to remain responsive without having to use Application.DoEvents.

Jeff Yates
+1  A: 

I think you might be able to catch the exception using the Application.ThreadException event.

To do it first you'll have to set Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); right at the start of the application, before any controls get created. Then add the event handler for the ThreadException event.

If this doesn't work I'd recommend using a BackgroundWorker which means you won't need to use DoEvents and it would also be better solution.

EDIT: In your example of the ThreadException handler you're throwing another exception. The idea was that you put your exception handling code in there, not raise another exception.

However I'd still urge you to use a BackgroundWorker to execute your loop code.

Jon Mitchell
+1  A: 

Not trying to bring up old news but because I have struggled with this same problem and didnt find a solution. If your running a background worker and your users are back in the main application thread working and doing other stuff and they want to run that same function that lies on the background worker, Application.DoEvents() seems like the best way and you can still use it. The problem lies within the fact that you have code in the RunWorkerCompleted event. When your cancelling the worker itself with CancelAsync anything that exists within RunWorkerCompleted is going to get executed. The problem is that usually whatever is in your RunWorkerCompleted is accessing the GUI and you cant access the GUI while running Application.DoEvents(), removing your code from RunWorkerCompmleted and incorporating it into the ReportProgress function is more acceptable. However, you need to throw check events right before your reportprogress function to make sure the main thread is not requesting another run. So something like this right before reportprogress:

if (backgroundWorker1.CancellationPending == true){
     e.Cancel = true;
     return;
}
backgroundWorker1.ReportProgress(0);

So you background thread is doing all its work and then right before you have it ReportProgress throw this check in there. This way if your user has said hey I actually want to run this query on another item they would be waiting in a loop with something like this:

if (backgroundWorker1.IsBusy == true){
     backgroundWorker1.CancelAsync();
     While(backgroundWorker1.CancellationPending == true){
          Application.DoEvents();
     }
}

While this is apparently a bad practice to use it accomplishes getting rid of the exception. I searched all over for this until I started modifying a project I was working on and saw that the only reason it was erroring was because its accessing the main thread at the same time, I BELIEVE. Probably will get flamed for this but there is an answer.

heavylifting