使用Application.DoEvents()


272

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

该功能是否可以让GUI跟VB6的其他方式一样赶上应用程序的其余部分DoEvents


35
DoEvents是Windows窗体的一部分,而不是C#语言的一部分。因此,可以从任何.NET语言使用它。但是,不应在任何.NET语言中使用它。
Stephen Cleary

3
是2017年。请使用Async / Await。有关详细信息,请参见@hansPassant答案的末尾。
FastAl

Answers:


468

Hmya,DoEvents()持久的奥秘。人们对此有强烈的反对意见,但是没有人真正解释它为什么是“不好的”。与“不要变异结构”一样的智慧。嗯,为什么运行时和语言如此糟糕,为什么支持对结构进行突变?同样的原因:如果做得不好,就会用脚开枪射击自己。容易。而正确做到这一点需要确切地知道它的作用,在DoEvents()的情况下,这绝对不容易理解。

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

大多数程序员都希望使用DoEvents在编写自己的模态循环时阻止其用户界面冻结。它确实做到了;它分派Windows消息并获得所有绘画请求。但是,问题在于它不是选择性的。它不仅可以发送绘画消息,还可以传递其他所有消息。

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

还有更多:用户可以单击相同的菜单项或按钮,以使相同的循环开始。现在,您有两个执行DoEvents()的嵌套循环,上一个循环已暂停,新循环从头开始。那可能行得通,但是男孩的可能性很小。尤其是当嵌套循环结束并且挂起的循环恢复时,尝试完成已经完成的作业。如果那没有例外,那么可以肯定地将数据全部乱七八糟。

回到ShowDialog()。它执行DoEvents(),但请注意它还会执行其他操作。除了对话框之外,它禁用了应用程序中的所有窗口。现在解决了三英尺的问题,用户无法做任何事情来弄乱逻辑。解决了关闭窗口和重新开始工作两种故障模式。换句话说,用户没有办法使程序以不同的顺序运行代码。就像您测试代码时一样,它将可预测地执行。它使对话非常烦人。谁不讨厌激活对话框并且不能够复制和粘贴其他窗口中的内容?但这就是价格。

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

C#和VB.NET的下一版本将使用新的await和async关键字提供另一种方式。一部分是由DoEvent和线程引起的麻烦启发的,另一部分是由WinRT的API设计启发的,该API设计要求您在进行异步操作时保持UI更新。就像从文件中读取一样。


1
不过,这只是冰山一角。在Application.DoEvents向代码中添加一些UDP功能之前,我一直没有问题,这导致了此处描述的问题。我很想知道,如果有周围的方式使用DoEvents
darda 2013年

这是一个很好的答案,我唯一想念的只是对执行和线程安全设计或时间片的解释/讨论,但这很容易再次成为文本的三倍。:)
jheriko

5
比一般的“使用线程”受益于更实用的解决方案。例如,该BackgroundWorker组件可以为您管理线程,从而避免了大多数踩脚操作带来的多姿多彩的后果,并且它不需要使用最新的C#语言版本。
Ben Voigt

29

可以,但是这是hack。

请参阅DoEvents是否有害?

直接从devdev引用的MSDN页面进行:

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

因此,Microsoft警告不要使用它。

另外,我认为它是一种黑客,因为它的行为不可预测且容易产生副作用(这是由于尝试使用DoEvents而不是旋转新线程或使用后台工作程序而产生的)。

这里没有大男子主义-如果它作为一个强大的解决方案,我将无所不在。但是,尝试在.NET中使用DoEvents令我痛苦不已。


1
值得注意的是,该帖子来自2004年,当时是.NET 2.0之前的版本,BackgroundWorker有助于简化“正确”的方式。
贾斯汀

同意 此外,.NET 4.0中的Task库也相当不错。
RQDQ 2011年

1
如果Microsoft提供了经常需要的功能,那为什么会成为黑客呢?
克雷格·约翰斯顿

1
@Craig Johnston-更新了我的答案,以更详细地说明为什么我认为DoEvents属于黑客类。
RQDQ 2011年

那篇恐怖的编码文章将其称为“ DoEvents spackle”。辉煌!
gonzobrains

24

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

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


13

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

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


好的答案,但是如果我可能会建议一个更好的进度条解决方案,我会说,在单独的线程中完成工作,使进度指示器在易失,互锁的变量中可用,并从计时器刷新进度条。这样,任何维护编码员都不会尝试将重量级代码添加到循环中。
mg30rg

1
如果您有大量的UI进程并且不想锁定UI,则不可避免地要使用DoEvents或等效项。第一种选择是不执行大量的UI处理,但这会使代码的可维护性降低。但是,await Dispatcher.Yield()与DoEvents的作用非常相似,并且实质上可以允许UI进程锁定屏幕,以实现所有意图和目的的异步。
墨尔本开发商

11

是。

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


3
如果您想要它,以便可以旋转并等待工作在另一个线程中完成,该怎么办?
jheriko

@jheriko然后,您真的应该尝试异步等待。
mg30rg

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怪异已经消失了。


3
但是,刷新不会更新窗口。如果用户在桌面上选择另一个窗口,则单击返回到您的窗口将没有任何效果,并且操作系统会将您的应用程序列为无响应。DoEvents()所做的不只是刷新,还因为它通过消息传递系统与OS进行交互。
ThunderGr 2013年

4

我已经看到许多使用“ DoEvents-Hack”的商业应用程序。尤其是在渲染开始发挥作用时,我经常看到以下内容:

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

他们都知道这种方法的弊端。但是,他们使用hack,因为他们不知道任何其他解决方案。下面是从采取了一些做法博客文章汤姆·米勒

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


3

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

1
调用doevents时,应始终阻止事件。否则,您的应用程序上的其他事件将作为响应触发,您的应用程序可能会立即开始执行两项操作。
FastAl

2

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

如果需要一些时间,它对于更新进度条并通知用户有关MainForm构造和加载的进度很有用。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.