wake-up-neo.com

使用Application.DoEvents()

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

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

249
Craig Johnston

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

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

13
Craig Johnston

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

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

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

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

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

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

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

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

446
Hans Passant

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

DoEvents是邪恶的吗?

直接来自 MSDN页面 that thedev references:

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

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

此外,我认为它是一个黑客,因为它的行为是不可预测的和副作用倾向(这来自尝试使用DoEvents而不是启动新线程或使用后台工作程序的经验)。

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

27
RQDQ

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

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

23
Bill W

是。

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

11
Frederik Gheysels

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

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

他们都知道那种方法的邪恶。但是,他们使用黑客,因为他们不知道任何其他解决方案。以下是从 博客文章中采取的一些方法Tom Miller

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

我在上面看到了jheriko的评论,并且最初同意我找不到一种方法来避免使用DoEvents,如果你最终旋转主UI线程等待在另一个线程上完成的长时间运行的异步代码片段。但是从马蒂亚斯的回答中,在我的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。

好/答案: 用简单的刷新调用替换DoEvents行,调用我的初始屏幕中间的小面板,FormSplash.Panel1.Refresh()。用户界面更新得很好,其他人警告的DoEvents怪异已经消失了。

4
TamW

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

3
thedev

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

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

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

2
Guest123

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