次の方法で共有


Keeping your UI Responsive and the Dangers of Application.DoEvents

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 https://msdn2.microsoft.com/library/k3aasx3f(en-us,vs.80).aspx
DataGridView virtual mode https://msdn2.microsoft.com/library/926z0fz5(en-us,vs.80).aspx
Background Worker https://msdn.microsoft.com/msdnmag/issues/05/03/AdvancedBasics/

Comments

  • Anonymous
    August 06, 2005
    Too bad the DoEvents docs doesn't mention anything like: This method is mostly used for workaround purposes. Use method/pattern X instead.

    There is a sample that suggests example use of "Call Application.DoEvents to force a repaint". I guess that's workaround when you need to "force". Isn't having such samples suggest that DoEvents is the correct way of doing x instead of being a workaround.

    It would be good if such workaround methods had a clear note like this blog entry that if you need to use it you may have a bad design or whatever.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemWindowsFormsApplicationClassDoEventsTopic.asp

  • Anonymous
    August 07, 2005
    Blog link of the week 31

  • Anonymous
    August 07, 2005
    If I need to create a window (not necessarily a Form, could be a NativeWindow) in a separate thread, it would need to run its own message loop -- something like the old PeekMessage/TranslateMessage/DispatchMessage loop.

    Looks like calling Application.Run() from that thread is the way to do that, but are there any special considerations when calling Application.Run() from another thread (other that the main one that already called it)?

    Would it be a good idea to do it at all?

  • Anonymous
    August 09, 2005
    The comment has been removed

  • Anonymous
    August 14, 2005
    Another great blog entry by Jessica Fosler: Keeping your UI Responsive and the Dangers of Application.DoEvents...

  • Anonymous
    August 14, 2005
    The comment has been removed

  • Anonymous
    August 15, 2005
    zzz - you have a great point RE the docs, I'll file a bug against the documentation. You're also welcome to file a bug using product feedback:
    http://lab.msdn.microsoft.com/productfeedback/

  • Anonymous
    August 15, 2005
    The comment has been removed

  • Anonymous
    August 15, 2005
    Jankrod -
    I dont know if it's possible, but I'd try moving the "heavy processing" to a background thread and update your UI in chunks. You may also want to Thread.Sleep the "heavy processing" thread to yield some time back to your UI thread so that it has the opportunity to be a little more responsive to users.

    In Whidbey the BackgroundWorker API helps make this easy, but it's just a fluffy layer on what was already out there in 1.0/1.1 - just use Invoke and/or BeginInvoke to communicate back with the main UI.

    An example is here
    http://blogs.msdn.com/jfoscoding/archive/2005/05/26/422405.aspx

  • Anonymous
    August 15, 2005
    ShadowChaser:

    Regarding the games thing, you might be right, although given a specific example there might be a workaround.

    Regarding hooking into the message loop, any class can implement IMessageFilter and respond to a particular window message.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwindowsformsimessagefilterclasstopic.asp

    See all about handles in windows forms as it may have some more tips for you here..
    http://blogs.msdn.com/jfoscoding/archive/2004/11/24/269416.aspx

  • Anonymous
    August 15, 2005
    jfo's coding : Keeping your UI Responsive and the Dangers of Application.DoEvents
    What&amp;rsquo;s the difference...

  • Anonymous
    August 16, 2005
    Thanks Jessica,

    Actually I had the ApplicationContext in mind but I was in a short intellectual coma when I was writing my question.

    Here's what I really wanted to ask: assuming that I do pass an ApplicationContext to a second thread (see the code below), are there any threading or other issues if both threads wait for the same (main) Form to close (within their respective Application.Run calls)?

    Code:

    <main thread>
    static void Main()
    {
    MyMainForm mainForm = new MyMainForm();
    ApplicationContext applicationContext = new ApplicationContext(mainForm);

    // Create second thread
    // Pass applicationContext to that thread, or somehow made it accessible from there

    Application.Run(); // or Application.Run(mainForm), I guess it doesn't make a difference (?)
    ...
    }

    <second thread>
    // Somewhere in the code, wait for the main form to close

    Application.Run(applicationContext); // same app context created in main()

  • Anonymous
    August 16, 2005
    Vladimir: I would hesitate to pass the same application context to a different thread - while I dont know of any issues offhand, it was not designed to be thread-safe: e.g. no one's taking locks at appropriate times.

    It's probably safer to roll your own application context here for the second thread.

  • Anonymous
    August 18, 2005
    jfo's coding : Keeping your UI Responsive and the Dangers of Application.DoEvents
    What’s the difference...

  • Anonymous
    August 24, 2005
    There were a lot of good comments/questions about keeping your UI responsive, if you're interested in...

  • Anonymous
    August 31, 2005
    Custom PaintingPainting best practices ComboBox OwnerDrawLayoutDock layout/Using the Splitter control...

  • Anonymous
    September 07, 2005
    When reading someone else's blog (even when searching for the answer to a specific problem) occasionally...

  • Anonymous
    September 29, 2006
    While doing the Vista Test Pass, I noticed a lot of timing and synchronization issues in the tests, and...

  • Anonymous
    July 20, 2007
    PingBack from http://blog.pnbconsulting.com.au/?p=54

  • Anonymous
    January 09, 2008
    PingBack from http://softwareblogs.intel.com/2008/01/09/parallel-fx-threading-for-newbies-like-me/

  • Anonymous
    May 08, 2008
    PingBack from http://windows.wawblog.info/?p=13087