【问题标题】:Why does Refresh() not do what DoEvents() does?为什么 Refresh() 不做 DoEvents() 做的事?
【发布时间】:2012-01-04 18:52:42
【问题描述】:

我正在尝试理解 Windows 窗体中某个长期存在的概念: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 ,等等。有没有cmets?

【问题讨论】:

  • DoEvents 告诉系统忽略事件?它不处理事件?那为什么在使用 DoEvents 之后 UI 开始变得响应?
  • 以下是我们自己的 Jeff Atwood 提供的更多背景信息:codinghorror.com/blog/2005/08/is-doevents-evil-revisited.html
  • @DJ KRAZE:欣赏你的 cmets,但你能说得更具体点吗?当你说“这只是跳过它们并刷新你的东西”时,你指的是 Refresh 还是 DoEvents?另外,我的问题不是关于要做什么,而是关于 Refresh 和 DoEvents 的幕后发生了什么。我熟悉当 UI 线程上的控件被更新时跨线程调用需要遵循协议的要求。
  • @DJKRAZE:哇,你的 cmets 不太连贯。但是,如果我对您的理解正确,您会说“如果有事件等待处理”,那么DoEvents()“只是跳过它们并刷新你的东西”。那是完全错误 -- 事实上,它与DoEvents() 实际所做的相反。 Wiktor Zychla 在他的回答中描述了这种行为,但DoEvents() 基本上设置了第二个消息泵循环,并泵送消息直到队列为空。这非常危险,正是因为它“跳过事件”——它处理队列中的任何内容,而不仅仅是“你的东西”。
  • 我混合了措辞,因为我陷入了试图制定关于引用我在 Delphi 解释中所做的事情的初步答案的过程中。抱歉造成混淆

标签: c# .net winforms multithreading invoke


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 嗯,一种方法可以专门让 WM_PAINT 进行调度:表单的 Update() 方法。或者 Refresh(),这里不需要。它没有调度 other 消息,使 Windows 相信 UI 线程没有发送消息并在 3 秒后显示幻影窗口。这是准确的,它不是。更多关于 DoEvents 的信息:stackoverflow.com/questions/5181777/use-of-application-doevents/…
  • @Wiktor:如果我们考虑不使用 DoEvents 的情况:应用程序本身的刷新请求将得到尊重。但是,当应用程序被强制进入后台,然后又回到前台时,UI 就会冻结。在此之前,即使应用程序在循环,刷新请求也会被兑现,但现在不是?
  • 我想这是我不明白的部分:你说“当你的处理程序完成时,消息循环肯定会捡起它并分派,从而重新绘制控件。”但为什么?应用程序很忙,尽管它偶尔会调用 Refresh。似乎响应重绘请求是调度程序唯一要做的事情。它选择性地回答重绘请求,仅此而已。这是正确的吗?
  • @Sabuncu:如果没有其余代码,就不可能说出回到前台时冻结的原因是什么。当您恢复应用程序时,它还会重新绘制窗口。由于某种原因,它使您的重绘失败。至于消息循环(调度程序) - 它调度所有传入的消息,而不仅仅是 WM_PAINT。
猜你喜欢
  • 2015-01-25
  • 1970-01-01
  • 1970-01-01
  • 2015-11-13
  • 1970-01-01
  • 2014-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多