为什么Refresh()不执行DoEvents()的功能?

时间:2012-01-04 18:52:43

标签: c# .net winforms multithreading invoke

我试图理解Windows Forms中的某个长期概念:UI编程;以下代码来自Chris Sells的Windows窗体编程书(第2版,2006年):

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(),为什么不处理等待注意的任何系统重绘事件?

还有一个后续问题:当我在this.Refresh()之后立即添加Application.DoEvents()时,冻结问题就会消失。这不需要求助于Invoke / BeginInvoke等等任何评论?

1 个答案:

答案 0 :(得分:5)

基本上,原因是Windows处理消息的方式 - 它在内部消息循环中以同步方式执行此操作。

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

另一方面,DoEvents()会触发消息循环的独立实例。它是从你的代码 中解雇的,这意味着调用堆栈如下所示:

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

内部消息循环处理所有待处理的消息,包括WM_PAINT(因此重绘了控件),但它很危险 - 因为它会发送所有其他待处理消息,包括按钮点击,菜单点击或使用右上角的X关闭应用程序的事件。遗憾的是,没有简单的方法可以使循环仅处理WM_PAINT,这意味着调用DoEvents()会使您的应用程序暴露于涉及意外用户活动的微妙潜在问题,这些问题会在您的代码执行期间触发DoEvents。