Is DoEvents Evil, Revisited

A colleague of mine had some excellent comments on the surprising reentrancy issues you'll run into when using Application.DoEvents():


This is a companion discussion topic for the original blog entry at: http://www.codinghorror.com/blog/2005/08/is-doevents-evil-revisited.html

I have seen plenty of solutions where DoEvents is used with total disregard for reentrancy. I noticed that you didn’t mention application close as a possible issue as well. We used to have all kinds of problems when programming in PowerBuilder (which had automatic garbage collection) and using its DoEvents equivalent. If you closed the program while within a call to DoEvents, you would end up getting all kinds of null reference issues when the DoEvents call would resume (after the program had closed). I don’t think that VB6 saw this type of issue as often but I wonder if .NET would be less forgiving?

I been programming visual basic for nearly 10 and c/c++ for 20 years and if you want to make your forms more responsive DoEvents is the easier way to go then a multi-thread solution.

Definitely having a busy property would help organize a form’s response. In my coding I call it Init and set it when I don’t want the form updated during some type of processing.

The best example of DoEvents is in the updating of the Progress Bar while loading a large file. Using a DoEvents every 1000 bytes or so gives a nice crisp response on the progress bar and is far easier to implement than a threaded solution.

I strongly recommend using a model-view-presentation setup where most of the UI work is done in the view and not the presentation (forms).

Yeah, you generally have to make very sure that all possible user input is either disabled or processed properly when you use DoEvents for progress bars or the like. Your button or main menu processing code must be aware of what might be happening elsewhere in the application, and how it should react.

Otherwise, users could execute a File menu command or click on the little “X” in the top right corner while the progress bar is counting up… and what happens to your app then?

you would end up getting all kinds of null reference issues when the DoEvents call would resume (after the program had closed)

True, and I think you can duplicate this with the sample solution linked in my post as well.

I’ve done a whole bunch of stuff with Delphi - and it has a direct equivilent: Application.ProcessMessages

I’ve found two alternatives to using a bare call, both of which help to avoid the reentrancy problems.

The first is to call MyForm.Update instead - don’t know if VB has a direct equivilent, but this processes ONLY paint events, nothing else. Great for ensuring that changes (say, to progress bars) are visible.

The second is to create a modal form that “hosts” the long running event. Being modal, all of the rest of the application is automagically disabled, and the only things the user can click are “Close” and “Cancel”.

Your colleague brings up a very good point about multiple re-entrancy. If you’re working in C# 2.0, it’s really best to avoid the entire issue altogether by using BackgroundWorker or an analogous solution.

In .NET, repainting is easy with this.Refresh();
Then why Application.Doevents(); ???

i have a problem with application.doevents
the debugger is not reading it

“In .NET, repainting is easy with this.Refresh()”

Refresh only posts a message requesting a refresh, the refresh certainly isn’t done by the time Refresh returns. You need to either return from the method or call DoEvents in order for the refresh to be actually actioned. Try something like this:

label1.Text = “One”;
Refresh();
Sleep(1000);

label1.Text = “Two”;
Refresh();
Sleep(1000);

label1.Text = “Three”;
Refresh();
Sleep(1000);

You’ll only ever see “Three”.

// some data we care about
private int _count;

// the click handler for a button
private void buttonUserAction_Click(object sender, System.EventArgs e)
{
buttonUserAction.Enabled = false;

_count++;

// _count won’t always be 1; it depends how many times
// this method was reentered during the DoEvents calls
Console.WriteLine(_count);

// simulate longer tasks that we are polling the status on.
// call DoEvents to allows the window to repaint
for (int i=0; i 100000; i++)
Application.DoEvents();

_count–;
buttonUserAction.Enabled = true;
}

Great posting, I have learned a lot from it, Thanks a lot!

I once worked on an application (.Net 2.0) that used the Application.DoEvents routine in conjunction with a message filter (IMessageFilter) that let all messages through except mouse clicks, keypresses, and window close requests.

Long tasks were run in a background thread while the foreground thread continued spinning in Application.DoEvents with the message filter. The UI thread would wait until the background thread finished.

This allowed the UI to update/paint (change progress bars, etc.), but the user could not perform any actions since all of the mouse clicks and keypresses were ignored by the message filter. When the background thread finished, the DoEvents loop (with message filtering) was exited and the application went back to normal. Any user events performed during the filtered processing were simply discarded.

The DoEvents/Filtering loop also had a simple mechanism that watched for specific events (ex. user pressed Escape specifically) that could be used to perform task cancellation (if the background processing thread supported it).

The mechanism seemed to work reasonably well and eliminated the typical issues associated with a stalled UI thread.