使用Application.DoEvents()

Application.DoEvents()可以在C#中使用吗?

这个函数是一种让GUI能够跟上应用程序其他部分的方法,就像VB6的DoEvents一样吗?

Hmya,DoEvents的持久神秘()。 对此有大量的反弹,但没有人真正解释过为什么这是“坏”的。 像“不要改变结构”一样的智慧。 嗯,为什么运行时和语言支持变异的结构,如果那么糟糕? 同样的理由:如果你做得不对,你就会在脚下自杀。 容易。 做正确的事情要求确切地知道它做什么,在DoEvents()的情况下,这绝对不是一件容易的事情。

几乎所有的Windows窗体程序实际上都包含对DoEvents()的调用。 这是巧妙的伪装,但是有一个不同的名字:ShowDialog()。 DoEvents()允许对话框模态化,而不会冻结应用程序中其余的窗口。

大多数程序员希望在编写自己的模态循环时使用DoEvents来阻止他们的用户界面冻结。 当然是这样; 它调度Windows消息并获取任何绘制请求。 然而问题在于它不具有select性。 它不仅分发油漆消息,而且还提供其他所有内容。

还有一组引发麻烦的通知。 他们来自显示器前方约3英尺。 用户可以例如closures主窗口,而调用DoEvents()的循环正在运行。 那工作,用户界面不见了。 但是你的代码并没有停止,它仍然在执行循环。 那很糟。 非常,非常糟糕。

还有更多的:用户可以点击相同的菜单项或button,导致相同的循环开始。 现在你有两个嵌套的循环执行DoEvents(),前面的循环被挂起,新的循环从头开始。 这可以工作,但男孩的几率是渺茫的。 特别是当嵌套循环结束并且暂停的循环结束时,试图完成一个已经完成的工作。 如果这不是一个例外爆炸,那么肯定是所有的数据搅乱地狱。

回到ShowDialog()。 它执行DoEvents(),但是请注意它做了别的事情。 它禁用应用程序中的所有窗口 ,而不是对话框。 现在,3英尺的问题解决了,用户不能做任何事情来搞乱逻辑。 解决了closures窗口和重新开始工作的失败模式。 或者换句话说,用户无法以不同的顺序让程序运行代码。 它会执行可预测的,就像你testing你的代码时一样。 它使对话非常烦人; 谁不讨厌有一个对话框活跃,不能从另一个窗口复制和粘贴的东西? 但是这是价格。

这就是在代码中安全地使用DoEvents所需要的。 将所有窗体的Enabled属性设置为false是避免问题的一种快速高效的方法。 当然,没有程序员喜欢这样做。 而不是。 这就是为什么你不应该使用DoEvents()。 你应该使用线程。 即使他们给你一个完整的武器库方式,以丰富多彩和深不可测的方式来打你的脚。 但是有利的是你只能自己动手; 它不会(通常)让用户拍摄她的。

C#和VB.NET的下一个版本将提供新的await和async关键字。 受到DoEvents和线程引起的麻烦,但很大程度上受WinRT的APIdevise的启发, 要求您在asynchronous操作发生时更新UI。 就像从文件中读取一样。

它可以,但它是一个黑客。

请参阅DoEvents邪恶吗?

直接从这个引用的MSDN页面 :

调用此方法会导致在处理所有等待窗口消息时暂停当前线程。 如果消息导致事件被触发,则可能会执行应用程序代码的其他区域。 这可能会导致您的应用程序出现难以debugging的意外行为。 如果执行需要很长时间的操作或计算,通常最好在新线程上执行这些操作。 有关asynchronous编程的更多信息,请参阅asynchronous编程概述。

所以微软警告不要使用它。

此外,我认为这是一个黑客行为,因为它的行为是不可预知的,副作用(这来自尝试使用DoEvents,而不是旋转一个新的线程或使用后台工作者)的经验。

这里没有男子气概 – 如果它作为一个强有力的解决scheme,我会全力以赴。 但是,试图在.NET中使用DoEvents,已经让我感到痛苦。

是的,在System.Windows.Forms命名空间的Application类中有一个静态DoEvents方法。 System.Windows.Forms.Application.DoEvents()可用于在UI线程中执行长时间运行的任务时处理在UI线程上等待队列中的消息。 这有益于使UI看起来更具响应性,而且在长时间运行时不会“locking”。 但是,这几乎总是不是最好的办法。 根据微软调用DoEvents“…导致当前线程挂起,而所有等待的窗口消息处理”。 如果事件被触发,那么就有可能出现难以追踪的意外和间歇性错误。 如果你有一个广泛的任务,最好是在一个单独的线程中做。 在单独的线程中运行长时间的任务,可以在不干扰用户界面继续顺畅运行的情况下处理这些任务。 在这里寻找更多的细节。

这里是一个如何使用DoEvents的例子; 请注意,Microsoft也提供了一个警告,不要使用它。

从我的经验,我会build议在.NET中使用DoEvents非常小心。 在包含DataGridViews的TabControl中使用DoEvents时,遇到了一些非常奇怪的结果。 另一方面,如果你正在处理的只是一个带有进度条的小表单,那么它可能是好的。

底线是:如果您打算使用DoEvents,那么您需要在部署应用程序之前对其进行彻底testing。

是。

但是,如果您需要使用Application.DoEvents ,则这通常表示应用程序devise不正确。 也许你想在一个单独的线程中做一些工作呢?

我见过很多商业应用,使用“DoEvents-Hack”。 特别是当渲染发挥作用时,我经常看到:

 while(running) { Render(); Application.DoEvents(); } 

他们都知道这种方法的坏处。 但是,他们使用黑客,因为他们不知道任何其他解决scheme。 以下是Tom Miller 博客文章中的一些方法:

  • 设置您的表单,让所有绘图都发生在WmPaint中,并在那里进行渲染。 在OnPaint方法结束之前,确保你做了一个this.Invalidate(); 这将导致OnPaint方法立即再次被触发。
  • P /调用到Win32 API并调用PeekMessage / TranslateMessage / DispatchMessage。 (Doevents实际上做了类似的事情,但是不需要额外的分配就可以做到这一点)。
  • 编写你自己的窗体类,它是CreateWindowEx的一个小包装,并给自己完全控制消息循环。 – 确定DoEvents方法适用于您,并坚持下去。

我看到上面的jheriko的评论,最初同意我找不到一种方法来避免使用DoEvents,如果你最终旋转你的主UI线程等待一个长时间运行的asynchronous代码在另一个线程上完成。 但是从Matthias的回答来看,简单的刷新我的UI上的一个小面板可以代替DoEvents(并且避免了一个令人讨厌的副作用)。

我的情况更详细…

我正在做以下(如这里所build议的),以确保在长时间运行的SQL命令期间更新进度条types启animation面( 如何显示“加载”覆盖… ):

 IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery(); while (!asyncResult.IsCompleted) //UI thread needs to Wait for Async SQL command to return { System.Threading.Thread.Sleep(10); Application.DoEvents(); //to make the UI responsive } 

糟糕:对于我来说,调用DoEvents意味着鼠标点击有时会在我的启animation面后面的窗体上进行触发,即使我将其设置为TopMost。

好/回答:用简单的刷新调用将DoEvents行replace为我的启animation面FormSplash.Panel1.Refresh()的中心的小面板。 用户界面更新很好,其他人警告的DoEvents怪异消失了。

查看Application.DoEvents方法的MSDN文档。

如果graphics处理以外的东西放在消息队列中,Application.DoEvents可能会产生问题。

如果需要一段时间,它可以用于更新进度条并通知用户MainForm构build和加载等进度。

在最近的一个应用程序中,我每次在MainForm的构造函数中执行代码块时,都会使用DoEvents更新加载屏幕上的一些标签。 在这种情况下,UI线程被占用,在不支持SendAsync()调用的SMTP服务器上发送电子邮件。 我可能已经用Begin()和End()方法创build了一个不同的线程,并且从它们中调用了Send(),但是这种方法很容易出错,我更喜欢我的应用程序的主表单,而不是在构造时抛出exception。