ProgressBar 在阻塞的 UI 线程中更新
为什么ProgressBar会在理论上阻塞的UI线程中更新?
在简单的应用程序中,我有一个进度栏和一个标签。我在 UI 线程中运行一个耗时的方法,尝试更新 ProgressBar 和标签。由于 UI 线程被阻塞,这不应该起作用。但进度条正在更新!
直到我在表单上执行任何操作并且它冻结之前,ProgressBar 都会更新(标签不会更新)。
这是为什么?
示例代码(在表单上放置一个按钮、一个进度条和一个标签):
private void button1_Click(object sender, EventArgs e)
{
while (true)
{
progressBar1.Value += 1;
label1.Text += "1";
Thread.Sleep(100);
}
}
进度条正在更新,标签没有。我的问题不是如何更新标签,而是为什么 ProgressBar 正在更新。我确实了解线程、DoEvents、async/await,但这不是答案。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为如果不拆卸一些 Windows 就很难完全回答这个问题,这对我来说现在的工作量太大了。
但基本上,当您在 WinForms ProgressBar 控件上设置 .Value 时,它所做的只不过是使用消息 1026 (PBM_SETPOS) 调用 SendMessage,该消息告诉 Windows 进度条设置其位置。
我得出的结论是,Windows 进度条会同步重绘自身以响应 PBM_SETPOS 以及响应 WM_PAINT。或者它可能在另一个线程上运行计时器以执行精美的眩光动画,并且能够重绘控件而无需等待绘制消息。
无论哪种方式,您所看到的只是 Windows 内部结构 - 在 WM_PAINT 处理程序之外绘制内容并不是一种不寻常的技术,即使它不是教科书上做事的方式。
实际上,查看 PBM_SETPOS 的文档 ( http:// /msdn.microsoft.com/en-us/library/bb760844(v=vs.85).aspx )它被记录为导致重绘 - 我猜这个是故意这样做的,以帮助懒惰/缺乏经验的人让阻塞进度条更新正常工作,而无需正确执行所有通常的麻烦。
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.
有不同的方法可以回答您的问题。让我解释一下。如果您正在运行一个冗长的任务,用户必须等待才能继续,那么您是否在 UI 线程上运行实际上并不重要。无论哪种方式,用户都必须等待任务完成才能继续,因此他们无论如何都无法使用该应用程序。
但是,在您可能希望用户在任务运行时与应用程序的不同部分进行交互的操作中,您可以创建一个工作线程来执行所述任务。您需要了解主 UI 线程用于处理 UI。绘制进度条是 UI 的一部分。 WinForms/.NET 的设计架构是通过BackgroundWorker 类创建后台线程,或者自行连接原始线程。这是设计使然。
然而,随着 C# 5.0 的 async 和 await 关键字以及 Task 对象的出现,这一切都将发生巨大的改变。您可以在channel9上搜索TechDays 11,或访问我的Facebook(发布了有关它的信息)一些帖子下来)。
如果您希望将操作保留在 UI 线程上,则可以在任务中调用 Application.DoEvents() 来帮助解决您的情况,以保持 Windows 消息流动,或者您可以以正确的方式执行此操作并实际正确实现线程。使用 Thread 类、委托和调用非常容易连接,甚至更容易使用 BackgroundWorker,它的唯一目的是在另一个线程上执行任务并报告 0/100% 渐进值。
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.