【问题标题】:Use of Application.DoEvents()Application.DoEvents() 的使用
【发布时间】:2011-07-08 02:17:51
【问题描述】:

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

这个函数是否是一种让 GUI 赶上应用程序其余部分的方法,就像 VB6 的 DoEvents 所做的一样?

【问题讨论】:

  • DoEvents 是 Windows 窗体的一部分,而不是 C# 语言。因此,它可以从任何 .NET 语言中使用。但是,它不应用于任何 .NET 语言。
  • 现在是 2017 年。使用 Async/Await。有关详细信息,请参阅@hansPassant 答案的结尾。

标签: c# winforms doevents


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    可以,但它是一个 hack。

    Is DoEvents Evil?

    直接来自the MSDN page 引用的thedev

    调用此方法会导致当前 线程全部挂起 等待窗口消息被处理。 如果消息导致事件发生 触发,然后你的其他领域 应用程序代码可以执行。这个可以 使您的应用程序展示 意想不到的行为 难以调试。如果你执行 需要的操作或计算 很长一段时间,通常最好 在新的设备上执行这些操作 线。有关更多信息 异步编程,见 异步编程概述。

    因此微软告诫不要使用它。

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

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

    【讨论】:

    • 值得注意的是,该帖子来自 2004 年,在 .NET 2.0 和 BackgroundWorker 帮助简化“正确”方式之前。
    • 同意。此外,.NET 4.0 中的任务库也相当不错。
    • 如果微软为经常需要的目的提供了一个功能,为什么它会成为黑客攻击?
    • @Craig Johnston - 更新了我的答案,更详细地说明了为什么我认为 DoEvents 属于黑客行为。
    • 那篇编码恐怖文章将其称为“DoEvents spackle”。太棒了!
    【解决方案3】:

    是的。

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

    【讨论】:

    • 如果你想要它怎么办,这样你就可以旋转并等待工作在另一个线程中完成?
    • @jheriko 那你真的应该试试 async-await。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      Hmya,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 更新。就像从文件中读取一样。

      【讨论】:

      • 不过,这只是冰山一角。我一直在使用Application.DoEvents 没有问题,直到我在我的代码中添加了一些UDP 功能,这导致了here 描述的问题。我很想知道DoEvents 是否有办法解决那个
      • 这是一个很好的答案,我觉得它唯一遗漏的是对产生执行和线程安全设计或一般时间片的解释/讨论 - 但这很容易又是文本的三倍. :)
      • 将受益于比一般“使用线程”更实用的解决方案。例如,BackgroundWorker 组件为您管理线程,避免了大多数丰富多彩的脚射结果,并且它不需要最先进的 C# 语言版本。
      • TL;DR:使用Application.DoEvents 是可以的,前提是您是一位认真、有纪律、受过良好教育且判断力强的开发人员。问题在于,Application.DoEvents 对于至少缺乏其中一种品质(至少暂时如此)的开发人员来说尤其具有吸引力。
      • @BenVoigt 相关问题:Async/await vs BackgroundWorker
      【解决方案6】:

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

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

      【讨论】:

      • 不错的答案,但是如果我可以为进度条的事情提出一个解决方案 更好 ,我会说,在一个单独的线程中做你的工作,让你的进度指示器在易失的、互锁的变量中可用,并从计时器刷新进度条。这样,没有维护编码员会试图将重量级代码添加到您的循环中。
      • DoEvents 或类似的东西是不可避免的,如果你有大量的 UI 进程并且不想锁定 UI。第一种选择是不进行大量的 UI 处理,但这会降低代码的可维护性。但是,await Dispatcher.Yield() 与 DoEvents 做了非常相似的事情,并且本质上可以允许为所有意图和目的异步锁定屏幕的 UI 进程。
      【解决方案7】:

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

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

      他们都知道这种方法的邪恶。但是,他们使用 hack,因为他们不知道任何其他解决方案。以下是blog post by Tom Miller 的一些方法:

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

      【讨论】:

        【解决方案8】:

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

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

        我正在执行以下操作(如建议的here)以确保在长时间运行的 SQL 命令期间更新进度条类型的初始屏幕 (How to display a "loading" overlay...):

        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。

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

        【讨论】:

        • 但是,Refresh 不会更新窗口。如果用户在桌面上选择另一个窗口,单击返回到您的窗口将没有任何效果,并且操作系统会将您的应用程序列为无响应。 DoEvents() 不仅仅是刷新,因为它通过消息系统与操作系统交互。
        【解决方案9】:

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

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

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

        【讨论】:

          【解决方案10】:

          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
              }
          }
          

          【讨论】:

          • 在调用 doevents 时应始终阻止事件。否则,您应用上的其他事件将作为响应触发,您的应用可能会同时开始做两件事。
          • 很好 - 但留下一个问题 - 我有一个辅助线程正在运行 - 并且需要在“那里”获取输入 - 除非使用 DoEvents() - 辅助进程不接收输入 - 我如何得到那个解决 ?当然我不想只为辅助线程编写自己的输入处理方法 - 如果主 UI 已经有输入处理
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多