【问题标题】:How to use DoEvents() without being "evil"?如何在不“邪恶”的情况下使用 DoEvents()?
【发布时间】:2012-07-06 07:52:23
【问题描述】:

一个简单的search for DoEvents 会带来很多结果,这些结果基本上会导致:

DoEvents 是邪恶的。不要使用它。请改用线程。

一般引用的原因有:

  • 重入问题
  • 性能不佳
  • 可用性问题(例如,在禁用的窗口上拖放)

但一些值得注意的 Win32 函数,例如 TrackPopupMenuDoDragDrop执行它们自己的消息处理以保持 UI 响应,就像 DoEvents 一样。
然而,这些似乎都没有遇到这些问题(性能、重入等)。

他们是怎么做到的?他们如何避免DoEvents 引用的问题? (或者他们?)

【问题讨论】:

标签: windows winforms winapi visual-c++ doevents


【解决方案1】:

回到 16 位 Windows 时代,当每个任务共享一个线程时,保持程序在紧密循环内响应的唯一方法是 DoEvents。正是这种非模态用法不鼓励使用线程。这是一个典型的例子:

' Process image
For y = 1 To height
    For x = 1 to width
        ProcessPixel x, y
    End For
    DoEvents ' <-- DON'T DO THIS -- just put the whole loop in another thread
End For

对于模态的东西(比如跟踪弹出窗口),它可能仍然可以。

【讨论】:

    【解决方案2】:

    我可能错了,但在我看来 DoDragDropTrackPopupMenu 是相当特殊的情况,因为它们接管了 UI,所以没有重入问题(我认为这是主要原因人们将DoEvents 描述为“邪恶”)。

    就我个人而言,我认为将某个功能视为“邪恶”并无济于事 - 而是解释其中的缺陷,以便人们自行决定。在DoEvents 的情况下,在极少数情况下使用它仍然是合理的,例如,在显示模式进度对话框时,用户无法与 UI 的其余部分进行交互,因此不存在重入问题.

    当然,如果你所说的“邪恶”是指“在没有完全理解陷阱的情况下不应该使用的东西”,那么我同意 DoEvents 是邪恶的。

    【讨论】:

      【解决方案3】:

      DoEvents() 是危险的。但我敢打赌,你每天都会做很多危险的事情。就在昨天,我引爆了一些爆炸装置(未来的读者:请注意相对于某个美国假期的原始发布日期)。小心,我们有时可以解释危险。当然,这意味着了解和理解危险是什么:

      • 重新进入问题。这里实际上有两个危险:

        1. 这里的部分问题与调用堆栈有关。如果您在一个循环中调用 .DoEvents(),而该循环本身处理使用 DoEvents() 的消息,依此类推,您将获得一个非常深的调用堆栈。 很容易过度使用 DoEvents() 并意外填满您的调用堆栈,从而导致 StackOverflow 异常。如果你只在一两个地方使用 .DoEvents() ,你可能没问题。如果它是您在长时间运行流程时使用的第一个工具,那么您很容易在这里遇到麻烦。即使在错误的地方使用一次也可能让用户强制产生 stackoverflow 异常(有时只需按住 enter 键),这可能是一个安全问题。
        2. 有时可能会在调用堆栈上找到两次相同的方法。如果你在构建方法时没有考虑到这一点(提示:你可能没有),那么可能会发生坏事。如果传入方法的所有内容都是值类型,并且不依赖于方法之外的东西,那么您可能会没事。但除此之外,您需要仔细考虑如果在调用 .DoEvents() 时将控制权返回给您之前再次运行整个方法会发生什么。您的方法之外的哪些参数或资源可能会被您意想不到的修改?您的方法是否更改了任何对象,堆栈上的两个实例都可能作用于同一个对象?
      • 性能问题。 DoEvents() 可以产生多线程的错觉,但它不是真正的多线程。这至少有三个真正的危险:

        1. 当您调用 DoEvents() 时,您将对现有线程的控制权交还给消息泵。消息泵可能反过来将控制权交给其他东西,而其他东西可能需要一段时间。结果是您的原始操作可能需要更长的时间才能完成,而不是它本身在一个永远不会产生控制权的线程中,肯定比它需要的时间更长。
        2. 工作重复。因为可能会发现自己运行相同的方法两次,而且我们已经知道这种方法很昂贵/运行时间很长(或者你一开始就不需要 DoEvents()),即使你考虑了所有提到的外部依赖项以上,因此没有不良副作用,您最终可能仍会重复大量工作。
        3. 另一个问题是第一个问题的极端版本​​:可能出现死锁。如果您的程序中的其他内容取决于您的进程完成,并且会阻塞直到完成,并且该内容由 DoEvents() 的消息泵调用,您的应用程序将卡住并变得无响应。这听起来可能有些牵强,但实际上意外地很容易发生意外,而且以后很难找到和调试崩溃。这是您在自己的计算机上可能遇到的一些应用挂起情况的根源。
      • 可用性问题。这些是由于未正确考虑其他危险而导致的副作用。这里没有什么新鲜的,只要你适当地看其他地方。

      如果你可以确定你解释了所有这些事情,然后继续。但实际上,如果 DoEvents() 是您寻求解决 UI 响应/更新问题的第一个地方,那么您可能没有正确解决所有这些问题。如果这不是您首先看到的地方,那么还有足够多的其他选择,我会质疑您是如何考虑 DoEvents() 的。今天,DoEvents() 的存在主要是为了与在其他可用选项之前出现的旧代码兼容,并作为尚未获得足够经验来接触其他选项的新程序员的拐杖。

      现实情况是,在大多数情况下,至少在 .Net 世界中,BackgroundWorker 组件几乎一样简单,至少在您完成一两次之后,并且它将以安全的方式完成工作。最近,async/await 模式或使用 Task 可以更加有效和安全,无需深入研究成熟的多线程代码靠自己。

      【讨论】:

      • 在模态循环中调度消息时不使用 您的主消息循环这一简单事实如何,因此您可能会做的所有特殊事情(MsgWaitForMultipleObjects任何人,或空闲处理)在模态循环完成之前不会完成?
      • 我在回复this question 时添加了关于重入问题的说明。
      • 当然,如果你不小心,线程也会很危险。
      猜你喜欢
      • 1970-01-01
      • 2012-05-01
      • 2011-09-06
      • 1970-01-01
      • 1970-01-01
      • 2010-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多