如何使用DoEvents()而不是“邪恶”?

时间:2012-07-05 20:45:30

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

一个简单的search for DoEvents会带来很多结果,主要导致:

  

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

一般引用的原因是:

  • 重新入侵问题
  • 表现不佳
  • 可用性问题(例如拖放已禁用的窗口)

但是TrackPopupMenuDoDragDrop 等一些值得注意的Win32函数会执行自己的消息处理,以保持用户界面的响应能力,就像DoEvents一样。 /> 然而,这些问题似乎都没有遇到过这些问题(表现,再入等等)。

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

3 个答案:

答案 0 :(得分:27)

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

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

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

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

如果 你可以确定你已经考虑了所有这些事情,那就继续吧。但实际上,如果DoEvents()是您首先要解决的UI响应/更新问题,那么您可能无法正确解决所有这些问题。如果它不是你看的第一个地方,还有足够的其他选项,我会质疑你是如何考虑DoEvents()的。

现实情况是,大多数情况下,至少在.Net世界中, BackgroundWorker 组件几乎一样容易,至少在你做过一次或两次之后,它将以安全的方式完成工作。最近,异步/等待模式或Task的使用可以更加有效和安全。

答案 1 :(得分:2)

回到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 :(得分:2)

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

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

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