Click here to Skip to main content
15,887,683 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
edited: please re-read


Hello fellow CPians!

I just answered the typical "why does my UI not update while my method is running"-Question. However, I did a quick test project and I was baffled to find out that there seems to be "some" updating happening. Imagine a System.Windows.Forms.Form with a Button, a ProgressBar, a ListBox and this sophisticated method:
C#
private void button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 100; i++)
    {
        progressBar1.Value = i;
        // alternatively, instead of the line above:
        listBox1.Items.Add(i);

        Thread.Sleep(100);
        // alternatively, instead of Sleep, with same result:
        for (int x = 0; x < 2000000000; x++);
    }
}

Nothing else is happening in the code; no calls to DoEvents() or whatever.

I would have imagined that there's no UI update at all while it's running but:

The ProgressBar actually does update. When trying to move the window the usual "(Not Responding)" appears in the caption and the visual update to the ProgressBar stops. Sometimes this also happens "on its own" (without trying to move the window) after some seconds.

The ListBox's vertical slider does update with the same behaviour as the ProgressBar. But the items only become visible after the method has executed.

Question: If the UI thread is busy executing the above method, why is there at first a visual update to the ProgressBar/ListBox and why does this stop when trying to move the window or sometimes on its own? And why is the painting of the items of the ListBox a different thing than its slider?
Posted
Updated 29-Apr-15 14:34pm
v4
Comments
Sergey Alexandrovich Kryukov 29-Apr-15 19:51pm    
This is a really provocative question. :-) Just one note: The UI thread is not really busy, just the opposite: the Sleep call puts it to the wait state where is does not use any CPU time. The problem is not "who is doing update?" progressBar1.Value assignment sends the invalidation request to the OS; it just does not take any considerable time. The question is: when WM_PAINT is handled? You never call DoEvents anywhere, right?
Intermediate conclusion: if you do something nasty, like in this example, the results could be hard to explain. But it does not make us free from explanations. :-)

Couple of silly questions: 1) when you run it, did you look at this application in Task Manager: are you sure there is only one thread? 2) Did you try it with something else, such as list box (add new item on each iteration)? what if this is the behavior specific only to ProgressBar? what if it does something weird, such as stupid Application.DoEvents() call — it would immediately explain everything.

—SA
Sascha Lefèvre 29-Apr-15 20:37pm    
I updated the question, please re-read.
The process has 4 threads (constant). This seems to be somehow normal; a new and unmodified WinForms-project had (curiously) 5 threads.
/Sascha
Sergey Alexandrovich Kryukov 29-Apr-15 20:48pm    
I have just answered somehow, after my own experiments you can look at.
I voted 5 for this question for opening the can of worms. :-)
—SA
Sergey Alexandrovich Kryukov 29-Apr-15 21:02pm    
Yes, this is also weird and cryptic. Well, if you show any File dialog box, it takes a separate thread, because, as you can observe, the changes in the file systems are eventually shown in such dialog, even if there is no user input in the application... This applications showed me 15 threads (!), but it was under the VS Host... :-)
—SA

1 solution

Here is what I found:

Add this method to the form:
C#
protected override void WndProc(ref Message m) {
    base.WndProc(ref m);
}


Make maximum big enough (on both progress bar and cycle), to give youself enough time.

After button press, put a break point at the first statement base.WndProc(ref m). You will see that the messages are pumped. And it won't happen when you replace assignment of the progress bar Value with, say, ListBox operations, as I suggested in my comment to the question.

So, here is how I can explain it: this property assignment does something special. What? On a next step, I checked up it if calls Application.DoEvents. No, it doesn't. I checked it up by updating both ListBox and ProgressBar. List view is still not rendered. When you use, for comparison, Application.DoEvents (commented out), you can see that list box items are rendered, as I expected.
C#
using System;
using System.Windows.Forms;
using System.Threading;

public partial class MainForm {

    ProgressBar pb = new ProgressBar();
    Button btn = new Button();
    ListBox lb = new ListBox();
    const int max = 100;

    public MainForm() {
        btn.Dock = DockStyle.Top;
        pb.Dock = DockStyle.Bottom;
        pb.Maximum = max;
        lb.Dock = DockStyle.Right;
        btn.Parent = this;
        pb.Parent = this;
        lb.Parent = this;
        btn.Click += (sender, eventArgs) => {
            for (int i = 0; i < max; i++) {
                lb.Items.Add(i);
                pb.Value = i;
                Thread.Sleep(100);
                //Application.DoEvents();
            }
        };
    } //MainForm

    protected override void WndProc(ref Message m) {
        base.WndProc(ref m); // put a break point here,
                             // after clicking the button
    } //WndProc

} //class MainForm

So, I can make a conclusion that the ProgressBar.Value assignment, in addition to invalidation, does something to pump specific messages through the message queue. Probably, it pumps only the messages specific to ProgressBar. What exactly? Since this moment, I lost interest to the "problem": you can find out further detail if you spy on all messages, or something like that. Anyway, I cannot see anything miraculous now. By the way, the sample code for effective spying could be found in my article on a completely different topic: Dynamic Method Dispatcher.

[EDIT]

And yes, we observed that the scroll bar is refreshed, in contrast to the TreeView content. It could be explained in the same way, if not one problem: I could not observe message pumping. Anyway, your question was about ProgressBar only. Some more investigation could be done for some other cases. ;-)

[END EDIT]

Now, the question is: why is it so? I would speculate that this functionality addresses the problem of alleviating lamers' problems: the progress bar could be designed the way it "refreshes no matter what", not matter what kind of misuse one could do to it. How about this idea?

—SA
 
Share this answer
 
v6
Comments
Sascha Lefèvre 29-Apr-15 20:56pm    
Thank you for your answer, Sergey. I will take a thorough look at it tomorrow, I need to go to bed ;-) Your last paragraph sounds plausible even for a tired mind though ;-)
/Sascha
Sergey Alexandrovich Kryukov 29-Apr-15 20:58pm    
:-)
Sascha Lefèvre 30-Apr-15 22:09pm    
Thank you again for your answer, Sergey, +5. I followed your explanation and made the same observations. Then I had the idea to take a look at the BCL source: The ProgressBar.Value-setter calls SendMessage(PBM_SETPOS) and PBM_SETPOS causes a redraw :-)
One thing is still nebulous to me though: Why does the redrawing stop when you move or try to move the window... do you have an idea there?
/Sascha
Sergey Alexandrovich Kryukov 1-May-15 0:00am    
Well, thank you.
Oh, this could be a matter of message priorities. Do know that WM_PAINT is passed in a special way? For example, several invalidation requests are merged to be reflected on paint only once. And you are inserting into a queue of the application spending most of the time in wait state a bunch of extra messages. A kind of stress, isn't it?..
—SA
Sascha Lefèvre 1-May-15 9:04am    
> Do know that WM_PAINT is passed in a special way? - Yes, I recently learned that reading an answer from you to someone else :-)
> A kind of stress, isn't it?.. - It shouldn't make such a fuss about some extra messages ;-)
/Sascha

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900