由Timer控件到Windows消息的思考

问题首先是由vb的Timer控件引起的。大家都知道vb不支持多线程,但Timer控件给我们一个错觉:一个Timer控

件就是一个线程,Timer控件是并行的。开始我也这么认为,但一段简单的代码说明了一切。

代码如下:

 

Option Explicit

Dim i As Long, j As Long

Private Sub Command1_Click()

Dim x As Double

For i = 1 To 1000000

    x = Sin(2) / i

    DoEvents

Next i

If i = 1000001 Then MsgBox "aaa"

End Sub

Private Sub Form_Load()

Timer1.Interval = 10

Timer1.Enabled = True

End Sub

Private Sub Timer1_Timer()

If i <> 0 Then Print "时钟事件开始:" & i

For j = 1 To 1000

    j = j

Next j

If i <> 0 Then Print ",时钟事件结束:" & i

End Sub

 

运行结果如下:

 

 

    下面我们分析一下代码,在按钮的单击事件中,是一个用来耗时的循环(是为了拖延时间,让我们有充足的时间来检验),循环中的DoEvents是在每次循环中都转交控制权,用来检测是否有其他等待的消息。If i = 1000001 Then MsgBox "aaa"是循环完成的标志(顺序执行至此表示循环已经完成)。在Timer事件中也有一个用来耗时的循环,在循环的开始和结尾分别放有检测i值的语句。

    我们发现在第一次检测完i的值后,经历了一个特意的耗时循环,循环完成后再检测i的值,和循环前相同,这说明:时钟事件程序开始执行,一直到执行结束,原来正在循环的程序将停止,即时钟事件程序开始时原正在执行的循环变量值将等于时钟事件程序结束时的值,即原正在执行的循环并没有继续循环。

    如果我们把在按钮的单击事件循环中的DoEvents注释掉,那么单击事件(循环)就不会交出控制权,那么直到整个单击事件结束后才会触发Timer事件,结果如图:

 

 

 

以上例子证明Timer事件不是并行的,而是串行的。在一个时间内cpu只能处理一个程序时间片在不同的程序间快速切换,形成了“Timer多线程”。就好比一名象棋大师,同时要和十个人下棋,十个棋盘一字排开,象棋大师走马灯一样在十个棋盘前穿梭落子,直到下完十盘棋局。象棋大师是CPU,十盘棋局是十个线程(或进程)任务。

如果我们继续深究原因的话,就会牵扯到Windows消息。

消息的循环过程大致为(关于消息的具体情况不再说明)

1. 消息循环调用GetMessage()从消息队列中查找消息进行处理,如果消息队列为空,程序将停止执行并等待(程序阻塞)。

2. 事件发生时导致一个消息加入到消息队列(例如系统注册了一个鼠标点击事件),GetMessage()将返回一个正值,这表明有消息需要被处理,并且消息已经填充到传入的MSG参数中;当传入WM_QUIT消息时返回0;如果返回值为负表明发生了错误。
    3. 取出消息(在Msg变量中)并将其传递给TranslateMessage()函数,这个函数做一些额外的处理:将虚拟键值信息转换为字符信息。这一步实际上是可选的,但有些地方需要用到这一步。
    4. 上面的步骤执行完后,将消息传递给DispatchMessage()函数。DispatchMessage()函数将消息分发到消息的目标窗口,并且查找目标窗口过程函数,给窗口过程函数传递窗口句柄、消息、wParam、lParam等参数然后调用该函数。
   5. 在窗口过程函数中,检查消息和其他参数,你可以用它来实现你想要的操作。如果不想处理某些特殊的消息,你应该总是调用DefWindowProc()函数,系统将按按默认的方式处理这些消息(通常认为是不做任何操作)。
   6. 一旦一个消息处理完成,窗口过程函数返回,DispatchMessage()函数返回,继续循环处理下一个消息。

有了这个基础我们就可以更加本质的看待这个问题

单击事件中的循环每次都要转交控制权(这个过程是非常快的),所以GetMessage()函数得以调用,造成GetMessage()函数“时时刻刻的存在”。但Timer控件是每10毫秒运行一次(也就是每10毫秒进入一次消息队列),所以每10毫秒才可以查找到这个消息,顺利的完成一次消息循环。这就达到了“Timer多线程”的效果。仔细观察可以发现,任意两个相邻Timer调用中i值的增量是大致相同的,更加有力的证明Timer的计时一直在进行,每10毫秒进一次队列。这给去掉按钮的单击事件循环中的DoEvents时的现象一个良好的铺垫,这时Timer的计时仍然在进行,但由于单击事件没有交出控制权,使得GetMessage()函数无法调用,而这时候DispatchMessage()函数无法返回(因为消息处理没有完成),所以Timer消息进了队列也没有任何响应,直到单击事件处理完毕后,Timer事件才被触发。

    后记:突然发现有一个问题没有说,就是在第一种情况下,为什么按钮循环心甘情愿等timer中的循环执行完了再执行呢?因为Doevents!按钮循环中有Doevents,可以检测有没有其他消息;而timer循环中没有Doevents,它会一直霸占CPU,直到循环执行完毕为止,也就是说,一个时间片不够,CPU会再给。

posted @ 2011-08-05 15:43  杨元  阅读(404)  评论(0编辑  收藏  举报