使用Application.DoEvents()

时间:2011-03-03 14:08:32

标签: c# winforms doevents

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

这个函数是否能让GUI跟上应用程序的其余部分,就像VB6的DoEvents一样?

10 个答案:

答案 0 :(得分:447)

Hmya,DoEvents()持久的神秘感。对它有强烈的反对意见,但没有人真正解释为什么它“糟糕”。与“不要改变结构”相同的智慧。嗯,为什么运行时和语言支持变异结构,如果那么糟糕?同样的理由:如果你做得不对,你会在脚下射击。容易。做正确的事情需要知道完全它做什么,在DoEvents()的情况下肯定不容易理解。

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

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

还有一组导致麻烦的通知。它们来自显示器前方约3英尺处。例如,当调用DoEvents()的循环正在运行时,用户可以关闭主窗口。这工作,用户界面消失了。但是你的代码没有停止,它仍在执行循环。那很糟。非常非常糟糕。

还有更多:用户可以单击导致相同循环开始的相同菜单项或按钮。现在你有两个执行DoEvents()的嵌套循环,前一个循环被挂起,新循环从头开始。这可行,但男孩的可能性很小。特别是当嵌套循环结束并且暂停的循环重新开始时,尝试完成已完成的作业。如果没有异常爆炸,那么数据肯定是乱七八糟的。

返回ShowDialog()。它执行DoEvents(),但请注意它执行其他操作。除了对话框之外,它禁用应用程序中的所有窗口。现在解决了3英尺问题,用户无法做任何事情来搞乱逻辑。关闭窗口和启动作业再次失败模式都得到了解决。或者换句话说,用户无法使程序以不同的顺序运行代码。它将以可预测的方式执行,就像测试代码时一样。它使对话非常烦人;谁不讨厌激活对话而无法从另一个窗口复制和粘贴东西?但那就是价格。

在代码中安全地使用DoEvent是什么。将所有表单的Enabled属性设置为false是避免问题的快速有效方法。当然,没有程序员真正喜欢这样做。并没有。这就是你不应该使用DoEvents()的原因。你应该使用线程。即使他们为你提供了一套完整的方法,以丰富多彩,不可思议的方式射击你的脚。但是你的优势在于你只能射击自己的脚;它不会(通常)让用户拍摄她的照片。

C#和VB.NET的下一个版本将使用新的await和async关键字提供不同的枪支。受到DoEvents和线程引起的麻烦在很大程度上受到启发,但在很大程度上受到WinRT的API设计的影响,需要在异步操作发生时保持UI更新。就像从文件中读取一样。

答案 1 :(得分:27)

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

请参阅 Is DoEvents Evil?

直接来自the MSDN page引用的thedev

  

调用此方法会导致当前   所有线程都被暂停   等待窗口消息被处理。   如果消息导致事件发生   触发,然后你的其他领域   应用程序代码可以执行这个可以   导致您的申请展出   出乎意料的行为   难以调试。如果你表演   采取的操作或计算   很长一段时间,它往往是优选的   在新的上执行这些操作   线。有关的更多信息   异步编程,请参阅   异步编程概述。

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

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

这里没有大男子主义 - 如果它作为一个强大的解决方案,我会全身心投入。但是,尝试在.NET中使用DoEvents只会给我带来痛苦。

答案 2 :(得分:23)

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

Here是如何使用DoEvents的示例;请注意,Microsoft也提醒您不要使用它。

答案 3 :(得分:13)

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

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

答案 4 :(得分:11)

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

答案 5 :(得分:4)

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

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

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

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

答案 6 :(得分:4)

我在上面看到了jheriko的评论,并且最初同意我找不到一种方法来避免使用DoEvents,如果你最终旋转主UI线程等待另一个线程上的长时间运行的异步代码完成。但是从马蒂亚斯的回答中,在我的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()。用户界面更新得很好,其他人警告的DoEvents怪异已经消失了。

答案 7 :(得分:3)

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

答案 8 :(得分:2)

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

如果需要一段时间,更新进度条并通知用户MainForm构造和加载等进度可能很有用。

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

答案 9 :(得分:1)

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

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

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

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