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

Meh*_*dad 23 windows winapi doevents visual-c++ winforms

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

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

一般引用的原因是:

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

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

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

Joe*_*orn 28

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

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

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

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

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

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

  • 如果你在模态循环中调度消息时没有使用**主消息循环**的简单事实怎么样,因此你可能做的所有特殊事情(`MsgWaitForMultipleObjects`任何人,或空闲处理)都不会完成,直到模态循环完成? (3认同)
  • 当然,如果你不小心,穿线也可能是危险的. (2认同)