25

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

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

一般引用的原因有:

  • 再入学问题
  • 表现不佳
  • 可用性问题(例如在禁用的窗口上拖放)

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

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

4

3 回答 3

31

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 模式或使用 aTask可以更加有效和安全,无需自己深入研究成熟的多线程代码。

于 2012-07-05T21:07:02.130 回答
4

回到 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

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

于 2012-07-05T20:54:10.953 回答
3

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

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

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

于 2012-07-05T21:04:33.983 回答