| 模式 | bManualReset | 行為 | 適用場景 |
| 自動重設 | FALSE | **單一**等待執行緒被喚醒後,**系統自動**將事件設回無信號。 | 工作項目佇列、單一消費者、避免驚群效應。 |
| 手動重設 | TRUE | 喚醒**所有**等待執行緒,事件**保持有信號**,直到程式碼呼叫 ResetEvent。 | 廣播通知 (關機、設定變更)、多執行緒同時啟動。 |
完整範例:可啟動/停止的計數執行緒
主視窗選單:「啟動執行緒」/「停止執行緒」/「離開」。背景執行緒迴圈計數,收到停止信號彈窗並重置等待。
.386
.model flat, stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h
.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE ; 全域停止旗標 (簡單同步,實際建議用 InterlockedExchange)
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ? ; 事件物件代碼
.code
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke GetCommandLine
mov CommandLine, eax
invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX, msg:MSG
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInst; pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszMenuName, OFFSET MenuName
mov wc.lpszClassName, OFFSET ClassName
invoke LoadIcon, NULL, IDI_APPLICATION; mov wc.hIcon, eax; mov wc.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW; mov wc.hCursor, eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx, WS_EX_CLIENTEDGE, ADDR ClassName, ADDR AppName, \
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, NULL, hInst, NULL
mov hwnd, eax
invoke ShowWindow, hwnd, SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu, hwnd; mov hMenu, eax
.WHILE TRUE
invoke GetMessage, ADDR msg, NULL, 0, 0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax, msg.wParam; ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg == WM_CREATE
; 1. 建立自動重設、初始無信號的匿名事件
invoke CreateEvent, NULL, FALSE, FALSE, NULL
mov hEventStart, eax
; 2. 建立背景執行緒
mov eax, OFFSET ThreadProc
invoke CreateThread, NULL, NULL, eax, NULL, 0, ADDR ThreadID
invoke CloseHandle, eax ; 不需保留執行緒代碼,關閉即可 (執行緒繼續跑)
.ELSEIF uMsg == WM_DESTROY
invoke PostQuitMessage, NULL
.ELSEIF uMsg == WM_COMMAND
mov eax, wParam
.if lParam == 0
.if ax == IDM_START_THREAD
invoke SetEvent, hEventStart ; 觸發事件 -> 執行緒喚醒
invoke EnableMenuItem, hMenu, IDM_START_THREAD, MF_GRAYED
invoke EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_ENABLED
.elseif ax == IDM_STOP_THREAD
mov EventStop, TRUE ; 設定停止旗標
invoke EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
invoke EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSEIF uMsg == WM_FINISH
invoke MessageBox, NULL, ADDR SuccessString, ADDR AppName, MB_OK
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax; ret
WndProc endp
ThreadProc PROC USES ecx Param:DWORD
; 1. 進入點即等待事件 (無限等待,不佔 CPU)
invoke WaitForSingleObject, hEventStart, INFINITE
mov ecx, 600000000 ; 計數迴圈上限
.WHILE ecx != 0
.if EventStop != TRUE
add eax, eax ; 模擬計算工作
dec ecx
.else
; 收到停止信號
invoke MessageBox, hwnd, ADDR StopString, ADDR AppName, MB_OK
mov EventStop, FALSE
jmp ThreadProc ; 重新進入等待 (遞歸跳轉,實際建議用迴圈)
.endif
.ENDW
; 正常完成
invoke PostMessage, hwnd, WM_FINISH, NULL, NULL
invoke EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
invoke EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
jmp ThreadProc ; 回到等待狀態
ret
ThreadProc ENDP
end start
程式碼走讀與關鍵點
- WM_CREATE 建立事件 + 執行緒:事件預設無信號,執行緒一啟動即在
WaitForSingleObject 睡眠,零 CPU 佔用。
- 自動重設:
CreateEvent(..., FALSE, FALSE, ...)。SetEvent 喚醒執行緒後,事件自動歸零,**無需手動 ResetEvent**,極適合「單次觸發」語意。
- 停止機制:範例使用全域
EventStop 旗標 (簡單但非執行緒安全)。生產代碼必須用 InterlockedExchange 或 Critical Section 讀寫共享旗標。
- 執行緒迴圈結構:範例用
jmp ThreadProc 重新進入 (會增長堆疊,長跑會爆棧)。正確寫法:外層無限迴圈包裹 WaitForSingleObject + 工作迴圈。
- UI 回饋:執行緒完成/停止時
PostMessage(WM_FINISH) 通知主視窗彈窗,避免在背景執行緒直接呼叫 MessageBox 導致 UI 死鎖/重入問題。
改良版執行緒架構 (推薦)
ThreadProc PROC USES ebx esi edi Param:DWORD
.WHILE TRUE ; 外層無限迴圈:等待啟動 -> 工作 -> 完成/停止 -> 繼續等待
invoke WaitForSingleObject, hEventStart, INFINITE
mov ecx, 600000000
.WHILE ecx != 0
; 使用 Interlocked 比較交換檢查停止旗標 (原子操作)
.if EventStop == 0 ; 簡化寫法,實際用 InterlockedCompareExchange
add eax, eax
dec ecx
.else
.break ; 跳出工作迴圈
.endif
.ENDW
.if EventStop != 0
invoke PostMessage, hwnd, WM_USER_STOPPED, 0, 0
mov EventStop, 0
.else
invoke PostMessage, hwnd, WM_FINISH, 0, 0
.endif
invoke EnableMenuItem, hMenu, IDM_START_THREAD, MF_ENABLED
invoke EnableMenuItem, hMenu, IDM_STOP_THREAD, MF_GRAYED
.ENDW
ret
ThreadProc ENDP
同步物件選型指南
| 物件 | 語意 | 典型用途 |
| Event | 狀態通知 (開/關) | 啟動/停止信號、工作完成通知、跨進程同步 (命名事件) |
| Mutex | 互斥擁有權 (遞歸可重入) | 保護共享資料結構、單一實例應用 (命名 Mutex) |
| Critical Section | 輕量級互斥 (進程內、非遞歸) | 高頻率共享記憶體存取 (比 Mutex 快 10x+) |
| Semaphore | 計數資源池 | 連線池、執行緒池限流、生產者/消費者緩衝區 |
| Waitable Timer | 時間觸發 | 定時任務、週期性喚醒、逾時處理 |
總結
- 事件物件是**最輕量、語意最清晰**的「通知」機制。
- 善用 自動重設 簡化狀態管理 (喚醒即歸零)。
- 背景執行緒**務必**在等待點 (
WaitForSingleObject) 睡眠,避免忙碌等待燒 CPU。
- 共享旗標/資料**必須**使用原子操作或 Critical Section 保護。
- UI 互動統一由主執行緒處理 (
PostMessage 通知),背景執行緒勿直接操作 UI。
- 上一篇文章: Shell 排序效能實測:組合語言 vs C 語言,執行速度與代碼體積對比
- 下一篇文章: Windows 反組譯實戰:W32Dasm 分析 GetCommandLine + MessageBox 程式,解析 IAT Thunk 機制與載入器重定位
Copyright ©
程式碼之家 All Rights Reserved