281

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

这个功能是一种允许 GUI 赶上应用程序其余部分的方法吗,就像 VB6 的DoEvents功能一样?

4

10 回答 10

484

嗯,DoEvents() 的持久奥秘。对它的强烈反对,但没有人真正解释为什么它是“坏的”。与“不要改变结构”相同的智慧。呃,为什么运行时和语言支持改变结构,如果这很糟糕?同样的原因:如果你做得不对,你会在自己的脚下开枪。容易地。正确地做这件事需要确切地知道它做了什么,这在 DoEvents() 的情况下绝对不容易理解。

马上开始:几乎所有 Windows 窗体程序实际上都包含对 DoEvents() 的调用。它被巧妙地伪装,但使用了不同的名称:ShowDialog()。正是 DoEvents() 允许对话框成为模态的,而不会冻结应用程序中的其余窗口。

大多数程序员希望在编写自己的模态循环时使用 DoEvents 来阻止用户界面冻结。它当然会这样做;它分派 Windows 消息并获取任何交付的绘制请求。然而问题是它没有选择性。它不仅发送绘画消息,它还提供其他所有内容。

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

还有更多:用户可以单击相同的菜单项或按钮来启动相同的循环。现在您有两个执行 DoEvents() 的嵌套循环,前一个循环暂停,新循环从头开始。这可能行得通,但男孩的可能性很小。特别是当嵌套循环结束并且暂停的循环恢复时,试图完成一项已经完成的工作。如果这没有例外,那么数据肯定会被打乱。

回到 ShowDialog()。它执行 DoEvents(),但请注意它做了其他事情。它禁用应用程序中的所有窗口,除了对话框。既然解决了 3 英尺的问题,用户就不能做任何事情来搞乱逻辑。解决了关闭窗口和重新开始工作的故障模式。或者换句话说,用户没有办法让你的程序以不同的顺序运行代码。它将以可预测的方式执行,就像您测试代码时所做的那样。它使对话框非常烦人;谁不讨厌对话框处于活动状态并且无法从另一个窗口复制和粘贴某些内容?但这就是价格。

这就是在您的代码中安全地使用 DoEvents 所需要的。将所有表单的 Enabled 属性设置为 false 是避免问题的一种快速有效的方法。当然,没有程序员真正喜欢这样做。而没有。这就是为什么你不应该使用 DoEvents()。你应该使用线程。即使他们为您提供了一套完整的方法来以丰富多彩且难以理解的方式射击您的脚。但好处是你只拍自己的脚;它不会(通常)让用户拍摄她的。

C# 和 VB.NET 的下一个版本将使用新的 await 和 async 关键字提供不同的枪。部分灵感来自 DoEvents 和线程造成的麻烦,但很大程度上受到 WinRT 的 API 设计的启发,该设计要求您在异步操作发生时保持 UI 更新。就像从文件中读取一样。

于 2011-03-03T16:35:11.140 回答
29

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

请参阅DoEvents 是否邪恶?.

直接来自thedev引用的MSDN页面

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

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

另外,我认为它是一种 hack,因为它的行为是不可预测的并且容易产生副作用(这来自尝试使用 DoEvents 而不是启动新线程或使用后台工作人员的经验)。

这里没有大男子主义——如果它是一个强大的解决方案,我会全力以赴。然而,尝试在 .NET 中使用 DoEvents 只会给我带来痛苦。

于 2011-03-03T14:10:15.433 回答
24

是的,在 System.Windows.Forms 命名空间的 Application 类中有一个静态 DoEvents 方法。System.Windows.Forms.Application.DoEvents() 可用于在 UI 线程中执行长时间运行的任务时处理在 UI 线程的队列中等待的消息。这样做的好处是使 UI 看起来更具响应性,并且在长时间任务运行时不会“锁定”。然而,这几乎总是不是最好的做事方式。根据微软调用 DoEvents 的说法,“......在处理所有等待窗口消息时导致当前线程暂停。” 如果触发事件,则可能会出现难以追踪的意外和间歇性错误。如果您有一项广泛的任务,最好在单独的线程中完成。在单独的线程中运行长任务可以在不干扰 UI 继续平稳运行的情况下处理它们。看在这里了解更多详情。

是一个如何使用 DoEvents 的示例;请注意,Microsoft 还警告不要使用它。

于 2011-03-03T14:19:23.433 回答
12

根据我的经验,我建议在 .NET 中使用 DoEvents 时要非常谨慎。在包含 DataGridViews 的 TabControl 中使用 DoEvents 时,我遇到了一些非常奇怪的结果。另一方面,如果您正在处理的只是一个带有进度条的小表单,那么它可能没问题。

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

于 2011-03-22T02:47:20.093 回答
11

是的。

但是,如果您需要使用Application.DoEvents,这主要表明应用程序设计不佳。也许您想在单独的线程中做一些工作?

于 2011-03-03T14:10:21.560 回答
5

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

关于我的案子的更多细节......

我正在执行以下操作(如此处所建议)以确保在长时间运行的 SQL 命令期间更新进度条类型的初始屏幕(如何显示“正在加载”覆盖... ):

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 意味着鼠标点击有时会在我的初始屏幕后面的表单上触发,即使我将其设为 TopMost。

好/答案:用一个简单的 Refresh 调用替换 DoEvents 行,该调用位于我的初始屏幕中心的一个小面板上,FormSplash.Panel1.Refresh(). UI 更新得很好,其他人警告过的 DoEvents 怪异现象消失了。

于 2013-12-04T08:19:20.127 回答
4

我见过许多使用“DoEvents-Hack”的商业应用程序。尤其是在渲染开始发挥作用时,我经常看到这样的情况:

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

他们都知道这种方法的邪恶。但是,他们使用 hack,因为他们不知道任何其他解决方案。以下是来自Tom Miller的博客文章的一些方法:

  • 将您的表单设置为在 WmPaint 中进行所有绘图,并在那里进行渲染。在 OnPaint 方法结束之前,确保执行 this.Invalidate(); 这将导致 OnPaint 方法立即再次被触发。
  • P/Invoke 进入 Win32 API 并调用 PeekMessage/TranslateMessage/DispatchMessage。(Doevents 实际上做了类似的事情,但你可以在没有额外分配的情况下做到这一点)。
  • 编写您自己的表单类,它是 CreateWindowEx 的一个小包装器,并让您自己完全控制消息循环。- 确定 DoEvents 方法适合您并坚持下去。
于 2011-12-19T21:59:06.837 回答
3

查看 MSDN 文档以了解该Application.DoEvents方法。

于 2011-03-03T14:09:57.220 回答
3

DoEvents 确实允许用户单击或键入并触发其他事件,而后台线程是一种更好的方法。

但是,在某些情况下,您可能会遇到需要刷新事件消息的问题。我遇到了一个问题,当控件在队列中有消息要处理时,RichTextBox 控件忽略了 ScrollToCaret() 方法。

以下代码在执行 DoEvents 时阻止所有用户输入:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}
于 2019-01-03T05:08:36.447 回答
2

如果将图形处理以外的内容放入消息队列中,Application.DoEvents 可能会产生问题。

如果需要一段时间,它对于更新进度条和通知用户诸如 MainForm 构建和加载之类的进度很有用。

在我最近制作的一个应用程序中,每次在 MainForm 的构造函数中执行一段代码时,我都使用 DoEvents 来更新加载屏幕上的一些标签。在这种情况下,UI 线程忙于在不支持 SendAsync() 调用的 SMTP 服务器上发送电子邮件。我可能已经使用 Begin() 和 End() 方法创建了一个不同的线程,并从它们调用了一个 Send(),但是该方法容易出错,我希望我的应用程序的主窗体在构造过程中不抛出异常。

于 2014-09-20T16:43:52.787 回答