这是一个很常见却也很经典的DoEvents引起的计算错误。我们用VB写程序时,有时为了能够在进行计算的过程中,同时让界面输出及鼠标和键盘的输入动作不致失去响应,往往会在循环过程中使用DoEvents释放cpu资源,以加快系统对无效区的绘制和对鼠标及键盘的处理,以达到良好的人机交流效果。但是,如果不当使用DoEvents,往往会给我们带来一些麻烦。先看一个代码:
代码很简单,也没有任何语法错误,就是点击一下按钮后,开始在一个循环中为一个模块级变量m_jia赋值,根据代码,我们可以推测出运行结束后,变量m_jia的值应该为10。
m_jia为10正是我们期望的正确结果,但是,它会一直正确吗?
答案是否。
这是因为程序在运行期间,我们不能保证用户由于失误,不会又一次点击了Command1按钮(也许是不小心碰着了,也许是小其它原因)。也许你会说,这有什么啊,不过多运行一次罢了,会对结果造成什么影响?
非也!
DoEvents本身的作用就是暂时释放cpu,在Command1_Click正在运行的期间,由于DoEvents的存在,所以如果用户又点击了一下Command1按钮,Command1_Click会又运行一次。只不过,在这个运行过程中,会有一个问题出现。
现在我们假设第一次开始执行Command1_Click,代码会把模块级变量m_jia初始为1,接下来运行循环,每循环一次,就为变量m_jia加1,当循环第5次,也就是i等于5时(同时m_jia值也为5),用户又一次点击Command1。此时VB会挂起正处于执行中Command1_Click中的代码,转而重新执行Command1_Click中的代码(我们且称为第二次执行Command1_Click),于是,循环计算器i会从1开始,而变量m_jia由m_jia=1语句初始化成了1,也从1开始计数,当第二次Command1_Click运行完后,VB再把cpu控制权还给第一次Command1_Click,此时变量i继续从5开始,由Next语句变成了6,而变量m_jia呢?它会从10开始(至于为什么,我后面再讲)。这样一来,当第一次Command1_Click运行结束,i变成了10,而m_jia变成了14。这个结果显然不是我们想要的。
原因就在于,DoEvents引起了代码重入,而在代码重入期间,有些变量受到了保护,而有些变量没有受到保护。那么,那些变量受到了保护呢?
答案是Command1_Click中的局部变量,当代码重入发生时,VB自动为所以局部变量生成副本,而非局部变量则不会。换句话说,在第一次Command1_Click和第二次Command1_Click的执行过程中,变量i根本就是两个变量,而变量m_jia还是原来的那一个(大家可以使用VarPtr观察两次运行期间变量i和m_jia的内存地址进行比较)。这样一来,同一个变量m_jia就会被两次的Command1_Click修改,结果当然不正确了。
解决办法是:
一、不在循环中使用DoEvents语句。
二、如果一定要使用DoEvents语句,那么,可以将m_jia设置成局部变量。
三、如果一定要使用DoEvents语句,且m_jia必须设置成模块级变量,那么,我们只能在Command1_Click开始处将Command1.Enabled属性设置为False,在Command1_Click代码结束处再将Command1.Enabled设置为True就OK了。