理論再豐富,不如一個能跑的範例。本文以兩個經典案例——**對話框時鐘**與**非阻塞檔案複製**——帶你走完「建立執行緒 → 執行任務 → 安全終止 → UI 回應」完整閉環,並對比單執行緒的痛點。
MultiThreadClock
// MultiThreadClockDlg.h
class CMultiThreadClockDlg : public CDialog {
HANDLE m_hThread;
DWORD m_dwThreadId;
HANDLE m_hStopEvent; // 優雅終止訊號
// ...
};
// MultiThreadClockDlg.cpp
UINT __cdecl ClockThread(LPVOID pParam) {
CMultiThreadClockDlg* pDlg = (CMultiThreadClockDlg*)pParam;
while (WaitForSingleObject(pDlg->m_hStopEvent, 0) != WAIT_OBJECT_0) {
CTime t = CTime::GetCurrentTime();
CString str = t.Format(_T("%H:%M:%S"));
// 跨執行緒更新 UI 必須用 PostMessage 或 SendMessage
pDlg->PostMessage(WM_USER_UPDATE_TIME, 0, (LPARAM)(new CString(str)));
Sleep(1000);
}
return 0;
}
void CMultiThreadClockDlg::OnStart() {
m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 手動重置、初始非觸發
m_hThread = CreateThread(NULL, 0, ClockThread, this, 0, &m_dwThreadId);
}
void CMultiThreadClockDlg::OnStop() {
SetEvent(m_hStopEvent); // 通知執行緒退出
WaitForSingleObject(m_hThread, INFINITE); // 等待實際結束
CloseHandle(m_hThread); m_hThread = NULL;
CloseHandle(m_hStopEvent); m_hStopEvent = NULL;
}
// 訊息處理
#define WM_USER_UPDATE_TIME (WM_USER + 100)
BEGIN_MESSAGE_MAP(CMultiThreadClockDlg, CDialog)
ON_MESSAGE(WM_USER_UPDATE_TIME, OnUpdateTime)
END_MESSAGE_MAP()
LRESULT CMultiThreadClockDlg::OnUpdateTime(WPARAM, LPARAM lParam) {
CString* pStr = (CString*)lParam;
SetDlgItemText(IDC_TIME, *pStr);
delete pStr; // 記得釋放
return 0;
}
Event 而非 TerminateThread,讓執行緒自行清理資源SetDlgItemText,必須 PostMessage 丟給 UI 執行緒處理CreateThread 的 lpParameter 傳 this,執行緒函式轉型取用單執行緒複製大檔案時,UI 凍結,「取消」按鈕點不動,使用者以為死機狂點。多執行緒讓複製在背景跑,UI 保持流暢。
struct CopyParam {
CString src, dst;
HWND hProgress; // 進度條控制代碼
HANDLE hStopEvent; // 取消訊號
HANDLE hDoneEvent; // 完成訊號
};
UINT __cdecl CopyThread(LPVOID pParam) {
CopyParam* p = (CopyParam*)pParam;
CFile srcFile(p->src, CFile::modeRead | CFile::shareDenyWrite);
CFile dstFile(p->dst, CFile::modeCreate | CFile::modeWrite);
ULONGLONG total = srcFile.GetLength(), done = 0;
BYTE buf[64*1024]; // 64KB 緩衝
while (done < total) {
if (WaitForSingleObject(p->hStopEvent, 0) == WAIT_OBJECT_0) break;
UINT nRead = srcFile.Read(buf, sizeof(buf));
if (nRead == 0) break;
dstFile.Write(buf, nRead);
done += nRead;
int percent = (int)(done * 100 / total);
::SendMessage(p->hProgress, PBM_SETPOS, percent, 0); // 直接送訊息更新進度條
}
srcFile.Close(); dstFile.Close();
SetEvent(p->hDoneEvent);
return 0;
}
// UI 啟動
void OnCopy() {
param.hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
param.hDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
param.hProgress = GetDlgItem(IDC_PROGRESS)->m_hWnd;
AfxBeginThread(CopyThread, ¶m);
}
// UI 取消
void OnCancel() { SetEvent(param.hStopEvent); }
// 等待完成 (可在另一執行緒或定時器輪詢)
WaitForSingleObject(param.hDoneEvent, INFINITE);
對超大檔 (GB 級) 或高併發,改用 FILE_FLAG_OVERLAPPED + ReadFileEx/WriteFileEx + 完成例程,完全無阻塞,CPU 佔用極低。
m_hStopEvent、案例二只共享 CopyParam 結構體Event、複雜資料結構用 CriticalSection/Mutex、生產消費用 Semaphore從一個每秒更新的時鐘,到一個可隨時取消的大檔複製,多執行緒的價值具象化為:**使用者感知不到的後台工作,與永不凍結的前台體驗**。掌握 CreateThread/AfxBeginThread、Event/Mutex、PostMessage 三板斧,配合「優雅啟動、優雅終止、跨執行緒 UI 安全」三原則,你就能駕馭 90% 的桌面並行場景。下一步,請嘗試將案例二改寫為 std::thread + std::atomic + lambda 現代 C++ 風格,感受 RAII 帶來的安心。