WIN32汇编语言教程:第12章 多线程 · 12.2 多线程编程(2)

这个程序很简单:当用户按下“计数”按钮的时候,WM_COMMAND消息处理代码调用_Counter子程序进行计数,子程序会将IDOK按钮上的文字通过SetWindowText函数改为“停止计数”,并且使用EnableWindow函数激活“暂停/恢复”按钮,然后进入计数循环。

在循环中,程序通过dwOption变量中的第1位(预定义为F_STOP)来判断是否停止,通过第0位(预定义为F_PAUSE)来决定是否暂停计数。这些标志位的状态以后会在按下“停止计数”或“暂停/恢复”按钮时在WM_COMMAND消息中设置。

粗看起来,程序天衣无缝,现在运行一下看看——“计数”按钮被正确地改为“停止计数”,“暂停/恢复”按钮也正确地被激活了,但是接下来就不对了,编辑框中并没有显示计数值,更糟糕的是接下来所有的按钮都无法按动,对话框也无法移动和无法关闭,总之,程序停止了响应,现在能结束它的惟一办法是通过任务管理器强制结束!

为什么会这样呢?这是因为主线程自从开始进入计数循环以后,就一直在那里“埋头苦干”,忙于计数工作,以至于把WM_COMMAND消息的处理抛到脑后去了,WM_COMMAND消息没有返回,对话框内部的消息循环就停留在DispatchMessage函数里面,以至于消息队列中的后续消息堆积在那里无法处理,这样不管用户按动“停止计数”按钮也好,移动对话框也好,这些动作虽然会被Windows检测到并转换成相应的消息放入消息循环中去,但是这些消息堆积在那里无法处理,所以就看不到对话框有任何的响应。

程序进入了一个怪圈:停止或暂停循环的条件是设置标志位,标志位是在按动“停止计数”或“暂停/恢复”按钮的WM_COMMAND消息中设置的,而WM_COMMAND消息被堆积在消息队列中无法处理,结果标志位永远不可能被设置,程序也就永远无法动弹了。虽然在程序一动不动的背后计数工作还在进行,显示计数值的SetDlgItemInt函数也不停地被调用,但是刷新对话框的WM_PAINT消息也同样没有被处理,所以编辑框中的计数值也无法被显示出来。

这个“问题程序”是Win32编程中“1/10秒规则”的一个极端例子,1/10秒规则指窗口过程处理任何一条消息的时间都不应该超过1/10秒,否则会因为消息的堆积造成程序对用户动作的响应变得迟缓。如果一条消息的处理时间超过1/10秒,那么就最好采取别的方法来完成,第04章中介绍的在消息循环中使用PeekMessage来获取空闲时间的方法就是一种,另一种方法是使用定时器在指定的时间间隔中每次完成一小部分工作,但对于这两种方法,程序必须将一个长时间的工作划分成多个小的部分,每部分的操作时间应该少于1/10秒。

显然,这两种方法也不是很好的办法,因为在不同主频的计算机中,1/10秒时间内可以处理的工作量是不同的,如果按照300 MHz处理器设计每小部分工作的工作量,那么到1GHz处理器上运行时,空出来的时间就被浪费了。实际上,解决1/10秒问题的最好办法就是使用多线程编程技术,程序可以建立一个新的线程来完成时间可能超过1/10秒的工作。

12.2.2 多线程的解决方法

1. 改进后的程序

对于这个“问题程序”,如果让_Counter子程序在一个新的线程中执行,那么在WM_COMMAND消息的处理中,需要做的工作就仅是启动一个新的线程而已,线程启动后,窗口过程就可以马上返回,消息队列中的消息就可以继续得到处理了。与此同时,_Counter子程序则会在另一个线程中继续运行。

说得多不如做得多,现在用多线程的方法来改进前面的Counter程序,修改后的源代码在所附光盘的Chapter12\Thread目录中,其中Counter.rc文件的内容保持不变。汇编源程序Counter.asm则改为如下代码:

                .386
                .model flat, stdcall
                option casemap :none
;####################################################################
; Include 文件定义
;####################################################################
include       windows.inc
include       user32.inc
includelib    user32.lib
include       kernel32.inc
includelib    kernel32.lib
;####################################################################
; Equ 等值定义
;####################################################################
ICO_MAIN         equ    1000
DLG_MAIN          equ    1000
IDC_COUNTER    equ    1001
IDC_PAUSE     equ    1002
;####################################################################
; 数据段
;####################################################################
                .data?
hInstance     dd    ?
hWinMain          dd    ?
hWinCount     dd    ?
hWinPause     dd    ?
 
dwOption          dd    ?
F_PAUSE       equ    0001h
F_STOP        equ    0002h
F_COUNTING    equ    0004h
                .const
szStop         db     '停止计数',0
szStart        db     '计数',0
;####################################################################
; 代码段
;####################################################################
                 .code
;####################################################################
_Counter         proc      uses ebx esi edi,_lParam       ;经过修改之处
 
                 or    dwOption,F_COUNTING
                and    dwOption,not (F_STOP or F_PAUSE)
                invoke SetWindowText,hWinCount,addr szStop
                 invoke EnableWindow,hWinPause,TRUE
 
                xor    ebx,ebx
                .while ! (dwOption & F_STOP)
                       .if    !(dwOption & F_PAUSE)
                               inc    ebx
                               invoke SetDlgItemInt,hWinMain,\
                                     IDC_COUNTER,ebx,FALSE
                         .endif
                 .endw
 
                invoke SetWindowText,hWinCount,addr szStart
                invoke EnableWindow,hWinPause,FALSE
                and    dwOption,not (F_COUNTING or F_STOP or F_PAUSE)
                 ret
 
_Counter          endp
;####################################################################
_ProcDlgMain   proc   uses ebx edi esi hWnd,wMsg,wParam,lParam
                 local  @dwThreadID
 
                mov    eax,wMsg
;********************************************************************
                .if    eax == WM_COMMAND
                       mov    eax,wParam
                        .if    ax ==  IDOK
                                .if    dwOption & F_COUNTING
                                     or    dwOption,F_STOP
                                 .else         ;经过修改之处
                                         invoke CreateThread,NULL,0,\
                                                 offset _Counter,NULL,\
                                                 NULL,addr @dwThreadID
                                         invoke CloseHandle,eax
                               .endif
                        .elseif ax ==  IDC_PAUSE
                               xor    dwOption,F_PAUSE
                        .endif
;********************************************************************
                 .elseif eax == WM_CLOSE
                       invoke EndDialog,hWnd,NULL
;********************************************************************
                 .elseif eax == WM_INITDIALOG
                         push      hWnd
                         pop    hWinMain
                         invoke GetDlgItem,hWnd,IDOK
                         mov    hWinCount,eax
                         invoke GetDlgItem,hWnd,IDC_PAUSE
                         mov    hWinPause,eax
;********************************************************************
               .else
                        mov    eax,FALSE
                       ret
              .endif
             mov    eax,TRUE
             ret
 
_ProcDlgMain   endp
;####################################################################
start:
             invoke GetModuleHandle,NULL
             mov    hInstance,eax
             invoke DialogBoxParam,eax,DLG_MAIN,\
                     NULL,offset _ProcDlgMain,NULL
             invoke ExitProcess,NULL
;####################################################################
             end start

上页:第12章 多线程 · 12.2 多线程编程(1) 下页:第12章 多线程 · 12.2 多线程编程(3)

第12章 多线程

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号