Application.DoEvents()

本文探讨了DoEvents在Windows窗体应用中的作用及其对用户体验的影响。通过实例对比,分析了DoEvents如何解决进程假死现象,同时指出其可能降低程序效率的问题。

在MSDN中的备注是:

当运行 Windows 窗体时,它将创建新窗体,然后该窗体等待处理事件。该窗体在每次处理事件时,均将处理与该事件关联的所有代码。所有其他事件在队列中等待。在代码处理事件时,应用程序并不响应。例如,当将另一窗口拖到该窗口前面时,该窗口不重新绘制。

 

如果在代码中调用 DoEvents,则您的应用程序可以处理其他事件。例如,如果您有向ListBox 添加数据的窗体,并将 DoEvents 添加到代码中,那么当将另一窗口拖到您的窗体上时,该窗体将重新绘制。如果从代码中移除 DoEvents,那么在按钮的单击事件处理程序执行结束以前,您的窗体不会重新绘制。

 

刚开始接触这段话的时候,会读不懂。所以我就去找了篇文章,文章中通过一个循环输出文本的例子解释了通过这段代码可以给人带来很好的用户体验( 不加的话程序只会显示输出循环最后的结果,但是加了以后会实时的输出),从而消除了认为进程死掉的情况。他还提到了用这个的缺点是影响了进程的效率,并通过了一个测验证实他的观点。

 

看了他的博客,我也就仿照他的说明写了个代码进行了测验,第一个代码是同他说的一样,加不加代码的效果是不同的。但是第二个,因为用一个计时器来计时。我因为不是很懂计时器,我就写了个类似计时器的代码,原理感觉上和他的差不多。

关于Application.DoEvents() - steed_zgf@126 - 守望幸福的猪
 1 private void button1_Click(object sender, EventArgs e)
 2         {
 3             this.timer1.Start();
 4             this.timer1.Interval = 1000;
 5             this.timer1.Tick += new EventHandler(timer1_Tick);
 6             for (int q = 0; q < 10000; q++)
 7             {
 8                 textBox1.Text = q.ToString();
 9                 Application.DoEvents();
10                 if (q == 9999)   
11                 {
12                     this.timer1 .Stop ();
13                   }
14             }
15         }
16 
17 private void timer1_Tick(object sender, EventArgs e)
18         {
19             num++;
20            label2.Text = num.ToString();
21            //MessageBox.Show("hehe!");
22         }
23 

原理是:用Timer组件,写了一个Tick事件并设置了间隔时间,在事件内用了一个counter来计数,再通过计数的结果已经响应的时间间隔来计算时间。再运行加了DoEvents代码的运行的很好,和我想的一样。

然后我又写了一个

关于Application.DoEvents() - steed_zgf@126 - 守望幸福的猪
 1 private void button2_Click(object sender, EventArgs e)
 2 
 3         {
 4 
 5             this.timer1.Start();
 6 
 7             this.timer1.Interval = 1000;
 8 
 9             this.timer1.Tick += new EventHandler(timer1_Tick);
10 
11             for (int q = 0; q < 100000; q++)
12 
13             {
14 
15                 textBox2.Text = q.ToString();
16 
17                 if (q == 99999)
18 
19                 {
20 
21                     this.timer1.Stop();
22 
23                   }
24 
25             }            
26 
27            label2.Text = num.ToString ();
28 
29 

       运行后发现,textBox2.可以得到99999就是结果的值,但是 label2却一直是零,也就相当于没有触发Tick事件,这就把给搞昏了,我明明是设定了TimerStart而且也在循环之前调用的Tick 事件,这是为什么呢?我又试着将代码里的stop 给注释掉,再运行,在循环结束后可以运行Tick 事件了。从这个结果来看,开始以为Tick本来是可以运行的。因为Tick的触发是当指定的计时器间隔已过去而且计时器处于启用状态时发生。那么有可能时间间隔太长,导致时间间隔没解释,也就是Tick来不及触发。为了验证这个是否正确,我将时间间隔调小,但是结果还是那样,而且到后来我直接将Tick的事件触发代码带到了循环中,可结果还是那样。到此,我想也许是存在优先级的概念,是否循环这个进程的优先级比Tick的优先级高。之前在循环体内设置的到99999后的StopTimer,直接导致Tick刚要执行就被强行关掉了。因为MSDN上也查不到这方面的原因,我就此当做是结论了。

     

      从这个测验后再回头想DoEvents的功能,我就想DoEvents就好比实现了进程的同步。在不加的时候,因为优先级的问题,程序会执行主进程的代码,再执行Tick的代码,而加了以后就可以同步执行。

从这个测验后再回头想DoEvents的功能,我就想DoEvents就好比实现了进程的同步。在不加的时候,因为优先级的问题,程序会执行主进程的代码,再执行Tick的代码,而加了以后就可以同步执行。

 

再回想那位网友在博客里写到的:

http://www.cnblogs.com/datong/archive/2008/04/06/1139216.html

记得第一次使用Application.DoEvents()是为了在加载大量数据时能够有一个数据加载的提示,不至于系统出现假死的现象,当时也没有深入的去研究他的原理是怎样的,结果在很多地方都用上了Application.DoEvents(),今天看到了关于这方面的一些文章,知道我以前有些用法是不当的,有些地方需要慎用Application.DoEvents()。
首先我们先看看在循环比较大的程序中,它的作用还是不错的,起到了一个实时响应的效果,例如:

for  ( int  q  =   0 ; q  <   1000000 ; q ++ )
            
{
                textBox1.Text 
= q.ToString();
                Application.DoEvents();//实时响应文本框中的值
            }

如果没有加上 DoEvents的话,由于循环时间会比较久就会出现假死的状态,而且程序不能处理其他的事件。而如果加上DoEvents的话就会对文本框的值实时响应,给用户带来较好的用户体验,可是DoEvents也带来了效率上的问题,处理同样的一个事件调用了DoEvents后效率降低了好几倍,这也是为什么要慎用的原因了。下面是我做的一个测试:

         private   void  button1_Click( object  sender, EventArgs e)
        
{
            expendTime.start();
            
for (int q = 0; q < 100000; q++)
            
{
                textBox1.Text 
= q.ToString();
                Application.DoEvents();
            }

            label2.Text 
= expendTime.ComputerTime();//计算耗时
        }


        
private   void  button2_Click( object  sender, EventArgs e)
        
{
            expendTime.start();
            
for (int q = 0; q < 100000; q++)
            
{
                textBox2.Text 
= q.ToString();
            }

            label3.Text 
= expendTime.ComputerTime();//计算耗时
        }


执行耗时对比:
从较大数据的循环中可以看出效率是很低的,所以如果能不调用DoEvents就尽量不用。也可以通过别的方法来处理的,例如多线程异步调用等。
MSDN中的定义:
当运行   Windows   窗体时,它将创建新窗体,然后该窗体等待处理事件。该窗体在每次处理事件时,均将处理与该事件关联的所有代码。所有其他事件在队列中等待。在代码处理事件时,应用程序并不响应。例如,当将另一窗口拖到该窗口前面时,该窗口不重新绘制。如果在代码中调用   DoEvents,则您的应用程序可以处理其他事件。例如,如果您有向   ListBox   添加数据的窗体,并将   DoEvents   添加到代码中,那么当将另一窗口拖到您的窗体上时,该窗体将重新绘制。如果从代码中移除   DoEvents,那么在按钮的单击事件处理程序执行结束以前,您的窗体不会重新绘制。   
 通常,您在循环中使用该方法来处理消息。
具体可参考这里

 

<think>嗯,我现在需要理解Application.DoEvents在VB.NET中的作用。首先,我记得在编程的时候,有时候程序在执行一个长时间的任务时,界面会变得没有响应,用户无法点击按钮或者其他元素,这显然不好。可能Application.DoEvents和这个问题有关? 让我想想,DoEvents这个词听起来像是处理事件的意思。那它的作用是不是让应用程序在处理长时间任务时,还能处理其他事件,比如用户输入或者界面更新?比如在一个循环中执行大量计算,如果在每次迭代后调用DoEvents,可能让界面保持响应? 不过,我记得有些地方说使用DoEvents可能会导致问题。比如,如果在处理某个事件的时候,用户又触发了另一个事件,可能会导致重入问题,也就是代码被再次进入,可能破坏数据的状态。所以虽然DoEvents能让界面响应,但需要谨慎使用。 那具体来说,DoEvents是怎么工作的呢?它应该是暂时让应用程序处理消息队列中的事件,比如重绘界面、处理点击事件等。比如在VB.NET中,当执行一个长时间循环时,如果不调用DoEvents,消息队列中的事件就会被阻塞,直到循环结束。而调用DoEvents后,系统会处理这些事件,让界面不卡住。 不过,有没有更好的方法替代DoEvents呢?比如使用后台线程或者异步编程。这样的话,长时间任务在后台运行,主线程保持响应,不需要频繁调用DoEvents。这可能更高效,也更安全,避免重入问题。 举个例子,假设有一个按钮点击事件处理程序,里面有一个循环处理大量数据。如果在循环中调用DoEvents,用户可能在这期间再次点击按钮,导致事件处理程序再次被调用,可能引发异常或数据错误。这种情况下,使用后台工作线程或者异步任务可能更合适。 另外,DoEvents是否适用于所有情况?可能在某些简单的场景下,比如短时间的任务,或者确保不会出现重入的情况下,使用DoEvents是方便的。但对于复杂的应用,尤其是需要处理大量用户交互的情况,最好还是采用更健壮的异步模式。 总结一下,Application.DoEvents的作用是允许应用程序在处理长时间操作时,继续处理UI事件,保持界面响应。但使用时需要注意潜在的重入问题,并考虑替代方案如多线程或异步编程来优化性能和稳定性。</think>`Application.DoEvents` 是 VB.NET 中的一个方法,主要用于在长时间运行的代码中保持用户界面(UI)的响应性。以下是详细介绍: --- ### **作用与原理** 1. **保持UI响应** 当执行耗时操作(如循环、复杂计算或I/O操作)时,UI线程会被阻塞,导致界面“冻结”。调用 `Application.DoEvents()` 会让应用程序暂时处理消息队列中的事件(如鼠标点击、界面重绘等),从而避免界面卡死。 2. **内部机制** - Windows 应用程序通过消息队列处理用户输入和事件(如按钮点击)。 - `DoEvents` 会强制刷新消息队列,临时处理未完成的事件(例如更新UI或响应用户操作),然后返回原代码继续执行。 --- ### **典型使用场景** ```vb.net For i As Integer = 1 To 1000000 ' 执行耗时操作... If i Mod 100 = 0 Then Application.DoEvents() ' 保持界面响应 ProgressBar1.Value = i / 10000 End If Next ``` 在此循环中,每隔100次迭代调用 `DoEvents` 更新进度条,并允许用户取消操作。 --- ### **潜在问题** 1. **重入(Reentrancy)问题** 如果用户在 `DoEvents` 期间再次触发事件(如点击按钮),可能导致事件处理程序被重复进入,引发数据竞争或逻辑错误。例如: ```vb.net Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click For i = 1 To 1000 Application.DoEvents() ' 用户可能在此时再次点击按钮 ' ... 操作可能被重复执行 Next End Sub ``` 2. **性能损耗** 频繁调用 `DoEvents` 会增加额外开销,尤其是在密集循环中。 --- ### **替代方案** 1. **异步编程(Async/Await)** 使用 `Async` 和 `Await` 关键字在后台执行耗时任务,避免阻塞UI线程: ```vb.net Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Await Task.Run(Sub() For i = 1 To 1000000 ' 后台处理... Next End Sub) ProgressBar1.Value = 100 End Sub ``` 2. **BackgroundWorker 组件** 通过 `BackgroundWorker` 在后台线程执行任务,并通过事件报告进度: ```vb.net Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork For i = 1 To 1000000 BackgroundWorker1.ReportProgress(i \ 10000) Next End Sub Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged ProgressBar1.Value = e.ProgressPercentage End Sub ``` --- ### **最佳实践** - **避免滥用**:仅在简单场景中短时间使用 `DoEvents`,确保不会引发重入问题。 - **优先异步**:对于复杂应用,使用异步编程或后台线程代替 `DoEvents`,提升安全性和性能。 - **禁用控件**:在耗时操作开始时禁用相关按钮/控件,防止用户误操作。 --- ### **总结** `Application.DoEvents` 是一种快速解决UI卡顿的临时方案,但需谨慎使用。现代VB.NET开发中,更推荐通过异步模式或多线程技术实现流畅的界面响应。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值