ProgressBar updates in blocked UI thread

2019-02-27 10:17发布

问题:

Why does the ProgressBar update in theoretically blocked UI thread?

In simple app I have a ProgressBar and a Label. I run a time-consuming method in UI thread which tries to update the ProgressBar and the label. This is not supposed to work, because of blocked UI thread. But the ProgressBar is updating!

Until I do anything on the form and it freezes, the ProgressBar updates (the label does not).

Why is that?

Example code (put a Button, a ProgressBar and a Label on form):

private void button1_Click(object sender, EventArgs e)
{
    while (true)
    {
        progressBar1.Value += 1;
        label1.Text += "1";
        Thread.Sleep(100);
    }
}

ProgressBar is updating, Label is not. My question is not how to make label update aswell, but why ProgressBar is updating. I DO know about Threads, DoEvents, async/await and that is not the answer.

回答1:

I think it's hard to answer this completely without disassembling a bit of Windows, which is too much work for me right now.

But basically, when you set .Value on an WinForms ProgressBar control, it does little more than call SendMessage with message 1026 (PBM_SETPOS), which tells the Windows progress bar to set its position.

I would conclude that the Windows progress bar redraws itself synchronously in response to PBM_SETPOS as well as in response to WM_PAINT. Or perhaps it's running a timer on another thread in order to do the fancy glare animation, and that's able to redraw the control without waiting for a paint message.

Either way, it's just Windows internals you're seeing - and drawing stuff outside WM_PAINT handlers is not that an unusual technique, even though it's not the textbook way of doing things.

Actually, looking at the docs for PBM_SETPOS ( http://msdn.microsoft.com/en-us/library/bb760844(v=vs.85).aspx ) it's documented as causing a redraw - I guess this is done deliberately to help the lazy/inexperienced to get blocking progress bar updates to work without all usual hassle of doing it properly.



回答2:

There are different approaches to answering your question. Let me explain. If you are running a lengthy task where the user has to wait to proceed, it actually doesn't matter whether you are running on the UI thread or not. Either way, the user has to wait for the task to complete to proceed, so they cannot use the application anyways.

However, in operations where you may want the user to interact with different parts of the application while the task is running, you create a worker thread to perform the said task. You need understand that the main UI thread is for, well, processing the UI. Painting a progress bar is part of the UI. The designed architecture of WinForms/.NET is that you create a background thread by means of the BackgroundWorker class, or wireup raw threads on your own. Its by design.

This all, however, will change drasitcally with C# 5.0 with the async and await keywords, as well as Task objects. You can search up TechDays 11 on channel9, or visit my Facebook (Posted about it a few posts down).

You can, to help remedy your situation if you feel inclined to keep your operation on the UI thread call Application.DoEvents() in your task to keep the Windows Messages flowing, or you can do it the right way and actually implement threading properly. Its very easy to wireup using the Thread class, a delegate, and invoking, and even easier to use a BackgroundWorker which was pretty much architected with the sole purpose of performing a task on another thread and reporting a 0/100% progressional value.