如何在不"evil"的情况下使用 DoEvents() ?



DoEvents的简单搜索会显示许多结果,基本上可以得出:

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

通常引用的理由是:

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

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

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

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

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

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

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

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

现实情况是,大多数时候,至少在.Net世界中,BackgroundWorker 组件几乎同样简单,至少一旦你做了一两次,它将以安全的方式完成工作。最近,异步/等待模式或使用Task可以更加有效和安全,而无需自己深入研究成熟的多线程代码。

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

对于模态的事情(例如跟踪弹出窗口),它可能仍然可以。

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

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

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

最新更新