What’s the difference between Application.Run and Application.DoEvents? 

Application.Run continually processes window messages for your application, raising events as necessary.  Application.DoEvents processes all the window messages in the queue until it has run out of messages, then returns.

Making your CPU work too hard

Given this definition, one could conceivably suggest that Application.Run could be replaced with:

while(!quit) {
   Application.DoEvents();
}

Unfortunately, when you run the application, switching over to the while loop spikes the CPU to 100% utilization. 

Why? Under the covers, DoEvents “peeks” message until it sees no more on the queue, then stops.  PeekMessage returns immediately (after doing its administrative work processing sent messages, etc) – therefore not yielding to other threads that may have work to do.  On the other hand GetMessage/WaitMessage block waiting for the new message, allowing other processes/threads get their work done. 

Adding new codepaths that might be unhandled

Let’s take the example where you add a whole bunch of items to your listbox in a button click event.  It takes forever, and its not painting.  So you add an Application.DoEvents to force a refresh.  When you’re done you set the text of the first control on the form to “finished”.

       private void button1_Click(object sender, System.EventArgs e) {
            for (int i = 0; i < 10000; i++) {
                listBox1.Items.Add(i.ToString());
                Application.DoEvents();  // <-- Calling DoEvents processes other events that may occur
            }
            this.Controls[0].Text = "Done";
        }

 

What happens if the user clicks the close button?  Ooops!  IndexOutOfRangeException!  Clicking on the close box has invoked the form’s Dispose method, which has cleared the control collection.  What's worse is that clicking close on the form didn't stop the list box processing from finishing up - while the form was no longer visible, this handler was merrily keeping the application alive.

Why wouldn’t this normally happen?  The action of the user pressing the close button would not be processed until you’ve returned from the handler for the button click.

The codepath when it works:

Application.Run
    Process mouse up on button
          Fire ButtonClick event
              Add bunch of items to listbox
              Update the first control on the form
    Process mouse up on close box
          Dispose the form

The explosion after sprinkling in Application.DoEvents:

Application.Run
    Process mouse up on button
          Fire ButtonClick event
              Add some items to listbox
              Call Application.DoEvents to make it look more responsive
                   Process mouse up on close box
                       Dispose the form
              Add remaining items to listbox 
              Update the first control on the form  -- BOOM!  Controls collection has been cleared!

This is just one example of codepath gone bad, there’s plenty of others – say the user clicked the button a second time – the button click event would be called again while you’re still processing the first!

                 bool inButtonClick = false;
        private void button1_Click(object sender, System.EventArgs e) {
            if (inButtonClick) {
                MessageBox.Show("Being called when I'm already in here!!!");
            }
            inButtonClick = true;
            for (int i = 0; i < 10000000; i++) {
                listBox1.Items.Add(i.ToString());
                Application.DoEvents();  // <-- this call here allows the second click to process
            }
            inButtonClick = false;

        }

Ugh, I have a DoEvents, now how do I fix it?

So we’ve established there’s a real need for processing events, but we’ve established that calling it can cause more trouble than it solves.  What are some ways to fix it?

Ask yourself, why are you calling Application.DoEvents?

Need painting to happen right now.

Problem: You’ve written a custom control, you’ve changed some state, and it’s not painting fast enough for you.  The most common example of this is changing the look of the button when the mouse is released.  You change some flag that says it shouldn’t look “pressed” anymore, and have called Invalidate() but it’s not painting because someone has hooked up a long running click handler – the paint wont be processed until the click is done.  So you think, I’ll just call Application.DoEvents to process everything that needs to happen before firing a click event.  In actuality, all you need to happen right now is the paint event. 

Solution:  Use invalidate to invalidate the area that needs repainting, and if you just cant wait for the paint to happen, follow your call to Invalidate() with Update().  This will force a synchronous paint.

Want to do work after we’ve processed all the current events.

Problem:  You’ve created a form, and you want to do work after it’s been shown.  You figure, create the Form, show it, clear the events in the queue, then finish your processing.  Something like:
    Form1 f = new Form1();
    f.Show();
    Application.DoEvents();
    f.StartLoadingDataIntoForm();

Solution:  See if there is already an event that corresponds to when you want to do your work. In Whidbey, you can use the Shown event for form to do exactly this.  For the WebBrowser, use the DocumentComplete event.

If there is no event that matches up, you can use BeginInvoke (yes, even on the same thread!)  BeginInvoke is essentially a PostMessage, under the covers it adds on to the end of the list of things the app needs to do. 

Why is this the same as Application.DoEvents()?  You’re clearing out the list, then doing more work.  This just eliminates the step of clearing out the list yourself.  If something has happened, like the user has clicked the close box, windows will automatically clear out the rest of the work in its message queue, so you don’t have to worry about pending BeginInvokes after the control has gone away.

Have a whole bunch of UI to update, need to make the UI responsive whilst adding new items
 
Problem:  You have a whole bunch of data you want to put into some grid/listbox/listview/treeview, the act of which is making your UI choke.

Solution:  Try to suspend processing of newly added items.  Read up the documentation for the UI, it could be that it supports methods that help you add in bulk.  In particular, look for methods like as BeginUpdate/EndUpdate, SuspendLayout/ResumeLayout and AddRange. 

Consider VirtualMode if offered. Some controls offer Virtual mode (I believe ListView and DataGridView offer these in Whidbey) – the idea here is that rather than having rows/items for everything, only have rows/items for the data that the user is looking at right now.

Consider using the BackgroundWorker to fetch results, updating your UI in chunks.  If the processing work can be done on a background thread, use the new BackgroundWorker component to help offload the work to another thread.  Note you cannot create controls/items on the background thread and use them on the UI thread – the only work that can really be done here is fetching data/doing some other kind of data processing. 

Consider creating a ForegroundWorker: If it must be done on the foreground thread, consider using a similar technique to the background worker: chunk up your data into manageable pieces, then use a System.Windows.Forms.Timer to add in new results.   The nice thing here is that the WM_TIMER message is a low priority message, so the UI will have a chance to paint itself.

When is it OK to add an Application.DoEvents?
There are some workarounds you’ll find in MSDN that suggest using Application.DoEvents to clear up one problem or another.  Usually the crux behind these problems is that no one is pumping messages at the time (no one has called Application.Run).  In this case, because no one else is pumping messages, you won’t get the re-entrant code problems.


More information
ListView virtual mode
http://msdn2.microsoft.com/library/k3aasx3f(en-us,vs.80).aspx
DataGridView virtual mode http://msdn2.microsoft.com/library/926z0fz5(en-us,vs.80).aspx
Background Worker http://msdn.microsoft.com/msdnmag/issues/05/03/AdvancedBasics/