Answers:
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更新。就像从文件中读取一样。
BackgroundWorker
组件可以为您管理线程,从而避免了大多数踩脚操作带来的多姿多彩的后果,并且它不需要使用最新的C#语言版本。
可以,但是这是hack。
请参阅DoEvents是否有害?。
调用此方法将导致当前线程在处理所有等待窗口消息时被挂起。如果消息导致事件被触发,则应用程序代码的其他区域可能会执行。这可能会导致您的应用程序表现出难以调试的意外行为。如果执行耗时较长的操作或计算,通常最好在新线程上执行这些操作。有关异步编程的更多信息,请参见异步编程概述。
因此,Microsoft警告不要使用它。
另外,我认为它是一种黑客,因为它的行为不可预测且容易产生副作用(这是由于尝试使用DoEvents而不是旋转新线程或使用后台工作程序而产生的)。
这里没有大男子主义-如果它作为一个强大的解决方案,我将无所不在。但是,尝试在.NET中使用DoEvents令我痛苦不已。
BackgroundWorker
有助于简化“正确”的方式。
是的,System.Windows.Forms命名空间的Application类中有一个静态DoEvents方法。在UI线程中执行长时间运行的任务时,可以使用System.Windows.Forms.Application.DoEvents()处理UI线程在队列中等待的消息。这样做的好处是,在执行长任务时,UI看起来更具响应性,并且不会“锁定”。但是,这几乎总是不是做事的最佳方法。据微软称,DoEvents“……导致当前线程在处理所有等待的窗口消息时被挂起。” 如果触发事件,则可能会出现难以跟踪的意外和间歇性错误。如果您有一项繁重的任务,最好在单独的线程中执行。在单独的线程中运行长任务,可以对其进行处理,而不会干扰UI继续平稳运行。看在这里了解更多详情。
这是一个如何使用DoEvents的示例;请注意,Microsoft还警告您不要使用它。
根据我的经验,我建议在.NET中使用DoEvents时要格外谨慎。在包含DataGridViews的TabControl中使用DoEvents时,我遇到了一些非常奇怪的结果。另一方面,如果您要处理的只是带有进度条的小表格,那么可能就可以了。
最重要的是:如果要使用DoEvents,则需要在部署应用程序之前对其进行彻底的测试。
我在上面看到了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怪异已经消失了。
我已经看到许多使用“ DoEvents-Hack”的商业应用程序。尤其是在渲染开始发挥作用时,我经常看到以下内容:
while(running)
{
Render();
Application.DoEvents();
}
他们都知道这种方法的弊端。但是,他们使用hack,因为他们不知道任何其他解决方案。下面是从采取了一些做法博客文章由汤姆·米勒:
- 设置表单以使所有绘图都在WmPaint中进行,然后在此处进行渲染。在OnPaint方法结束之前,请确保执行this.Invalidate();。这将导致立即再次触发OnPaint方法。
- P /调用Win32 API,然后调用PeekMessage / TranslateMessage / DispatchMessage。(Doevents实际上执行类似的操作,但是您可以在没有额外分配的情况下执行此操作)。
- 编写自己的窗体类,该窗体类是CreateWindowEx的小包装,并让您完全控制消息循环。-确定DoEvents方法适合您,并坚持使用。
请查阅MSDN文档中的Application.DoEvents
方法。
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
}
}
如果在消息队列中放置图形处理以外的其他内容,Application.DoEvents可能会产生问题。
如果需要一些时间,它对于更新进度条并通知用户有关MainForm构造和加载的进度很有用。
在我最近制作的一个应用程序中,每次在MainForm的构造函数中执行代码块时,我都使用DoEvents更新加载屏幕上的一些标签。在这种情况下,UI线程忙于在不支持SendAsync()调用的SMTP服务器上发送电子邮件。我可能用Begin()和End()方法创建了一个不同的线程,并从它们中调用了Send(),但是该方法容易出错,我希望我的应用程序的Main Form在构造期间不抛出异常。
DoEvents
是Windows窗体的一部分,而不是C#语言的一部分。因此,可以从任何.NET语言使用它。但是,不应在任何.NET语言中使用它。