I <3 Steve McConnell*
Coding Horror
programming and human factors
by Jeff Atwood

August 22, 2005

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():

The Application.DoEvents method is often used to allow applications to repaint while some longer task is taking place. This is usually the result of polling instead of using events / delegates. That's fine, but developers need to understand DoEvents processes all of the messages in the message queue, not just paint messages. This can lead to unexpected reentrancy issues. A simple example is shown below.

// some data we care about
private int _count;

// the click handler for a button
private void buttonUserAction_Click(object sender, System.EventArgs e)
{
  _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--;
}

In this example, it doesn't matter if the method is reentered. But it might in other methods or applications. Always remember that DoEvents can cause methods to be reentered. And understand which methods are affected: any method that is called directly or indirectly in response to processing messages in the message queue.

It would be useful if the Form object had a Busy property; the form would only process paint related messages and skip input related messages like menus, clicks, keyboard, etc.

I put together a VS.NET 2003 winforms solution (6kb) demonstrating the code sample above.

This may make you wonder: Is DoEvents Evil?

I think it's definitely the lesser of two evils: it's either this simplified cooperative yielding or full-bore multithreaded code. DoEvents can be a big win with minimal effort in the right situations. For example, how about using it to improve perceived form load performance?

Posted by Jeff Atwood    View blog reactions

 

« Clean Sources Plus Microsoft LogParser »

 

Comments

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).

Rob Conley on August 23, 2005 10:23 AM

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?

BlazingFox on August 23, 2005 08:52 PM

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?

Chris Nahr on August 24, 2005 08:19 AM

> 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.

Jeff Atwood on August 24, 2005 11:16 AM

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".

Bevan Arps on August 26, 2005 06:34 AM

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.

on January 11, 2006 02:38 PM

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

Shefeer on November 8, 2006 11:35 PM

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

serj on November 21, 2006 02:27 AM

"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".

Andy on February 1, 2007 03:09 AM

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

zippy on August 8, 2007 03:15 AM







(hear it spoken)


(no HTML)




Content (c) 2008 Jeff Atwood. Logo image used with permission of the author. (c) 1993 Steven C. McConnell. All Rights Reserved.