線程間通訊不僅限於資料共享,更包含執行順序協調、狀態變更通知等場景。事件物件是 Windows 核心同步物件中最適合「訊號-等待」模式的機制。本文詳細解析 MFC CEvent 類的手動/自動重置模式、跨線程訊號傳遞,並透過完整範例示範線程啟動控制與優雅終止。
CEvent 建構函數原型:
CEvent(
BOOL bInitiallyOwn = FALSE, // 初始狀態:TRUE=有訊號, FALSE=無訊號
BOOL bManualReset = FALSE, // TRUE=手動重置, FALSE=自動重置
LPCTSTR lpszName = NULL, // 跨進程名稱
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL
);
| 模式 | 行為特徵 | 適用場景 |
|---|---|---|
| 自動重置 | 單一等待線程被喚醒後自動重置為無訊號 | 工作佇列、單一消費者模式 |
| 手動重置 | 維持有訊號狀態直到明確呼叫 ResetEvent() | 廣播通知、多線程同步啟動 |
SetEvent():設定為有訊號狀態,喚醒等待線程ResetEvent():設定為無訊號狀態(手動重置模式必須)PulseEvent():瞬間設定並重置(自動重置模式喚醒單一線程,手動重置喚醒所有)WaitForSingleObject():等待事件進入有訊號狀態以下範例展示如何使用雙事件(啟動/終止)精確控制工作者線程:
// 全域事件物件
CEvent ThreadBegin; // 自動重置,初始無訊號
CEvent ThreadEnd; // 手動重置,初始無訊號
UINT ThreadProc(LPVOID param)
{
// 1. 等待啟動訊號(無限期阻塞)
::WaitForSingleObject(ThreadBegin.m_hObject, INFINITE);
::MessageBox((HWND)param, "Thread Activated!", "Thread", MB_OK);
BOOL KeepRunning = TRUE;
while (KeepRunning)
{
// 執行週期性工作...
// 2. 輪詢終止訊號(非阻塞檢查)
int Result = ::WaitForSingleObject(ThreadEnd.m_hObject, 0);
if (Result == WAIT_OBJECT_0)
KeepRunning = FALSE;
}
::MessageBox((HWND)param, "Thread Stopped!", "Thread", MB_OK);
return 0;
}
// 主線程控制邏輯
HWND hWnd = GetSafeHwnd();
AfxBeginThread(ThreadProc, hWnd); // 線程建立但阻塞於 ThreadBegin
ThreadBegin.SetEvent(); // 發送啟動訊號,線程開始執行
// ... 適當時機發送終止訊號 ...
ThreadEnd.SetEvent(); // 手動重置事件,線程下次輪詢即退出
GUI 線程不可長時間阻塞於 WaitForSingleObject,否則導致介面凍結。解決方案:
// 使用 MsgWaitForMultipleObjects 處理訊息佇列同時等待事件
while (TRUE)
{
DWORD result = MsgWaitForMultipleObjects(1, &hEvent, FALSE, INFINITE, QS_ALLINPUT);
if (result == WAIT_OBJECT_0) break; // 事件觸發
if (result == WAIT_OBJECT_0 + 1) // 有訊息到達
ProcessMessages();
}
建構時指定名稱即可實現跨進程訊號傳遞:
CEvent GlobalEvent(FALSE, TRUE, "Global\\MyApp_ShutdownEvent");
// 其他進程開啟同名事件
CEvent GlobalEvent(FALSE, TRUE, "Global\\MyApp_ShutdownEvent");
事件建立與線程啟動順序至關重要:
SetEvent() 釋放工作者線程事件物件是線程協調的瑞士刀,特別適合「等待條件滿足再執行」的場景。掌握手動/自動重置模式差異、善用輪詢與阻塞等待組合、注意 GUI 線程訊息處理,能構建出健壯且回應靈敏的多線程應用程式。