为什么Refresh()不像DoEvents()那样做

本文关键字:不像 Refresh 为什么 DoEvents | 更新日期: 2023-09-27 18:19:31

我正在努力理解Windows Forms re:UI编程中一个长期存在的概念;以下代码来自Chris Sells的《Windows窗体编程》一书(2006年第2版):

void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
  // Display progress in UI
  this.resultsTextBox.Text = pi;
  this.calcToolStripProgressBar.Maximum = totalDigits;
  this.calcToolStripProgressBar.Value = digitsSoFar;
  if( digitsSoFar == totalDigits ) {
    // Reset UI
    this.calcToolStripStatusLabel.Text = "Ready";
    this.calcToolStripProgressBar.Visible = false;
  }
  // Force UI update to reflect calculation progress
  this.Refresh();
}

此方法是小样本应用程序的一部分,该应用程序具有另一个计算Pi的长期运行方法。每次计算一组数字时,都会调用ShowProgress()来更新UI。正如书中所解释的,这段代码是"错误的"做事方式,当应用程序最小化,然后再次进入前台时,会导致UI冻结,导致系统要求应用程序重新绘制自己。

我不明白的是:既然this.Refresh()被反复调用,为什么它不处理任何等待关注的系统重新绘制事件

还有一个后续问题:当我在这个.Refresh()之后立即添加Application.DoEvents()时,冻结问题就消失了这不必求助于Invoke/BeginInvoke等。有什么意见吗?

为什么Refresh()不像DoEvents()那样做

基本上,这是因为Windows处理消息的方式——它在内部消息循环中以同步的方式进行处理。

关键是有一条消息触发了您的代码。例如单击按钮。您的应用程序正在处理消息。从该处理程序中,您可以强制刷新,将另一个WM_PAINT放入消息队列中。当处理程序完成时,消息循环肯定会接收并调度它,从而重新绘制控件。但是您的代码还没有完成,事实上它循环调用ShowProgress,导致WM_PAINT永远排队。

另一方面,DoEvents()会引发消息循环的一个独立实例。它是从代码中的触发的,这意味着调用堆栈看起来像这样:

外部消息循环->您的代码->内部消息循环。

内部消息循环处理所有挂起的消息,包括WM_PAINT(因此重新绘制控件),但这是危险的,因为它将调度所有其他挂起的信息,包括按钮单击、菜单单击或右上角X关闭应用程序的事件。不幸的是,没有简单的方法使循环仅处理WM_PAINT,这意味着调用DoEvents()会使您的应用程序暴露在微妙的潜在问题中,这些问题涉及触发DoEvents的代码执行过程中的意外用户活动。