首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

MFC 多執行緒安全終止全攻略:從 Worker 到 UI 執行緒

     編輯:關於C++

引言

在 MFC 多執行緒程式中,「如何優雅且安全地結束執行緒」是經典難題。強制終止 (TerminateThread) 會導致資源洩漏、關鍵區遺棄、C++ 物件解構函式未執行等嚴重後果。本文系統梳理 Worker 執行緒與 UI 執行緒的正確終止模式,並給出可直接套用的「旗標輪詢 + 事件等待」最佳實踐範例。

核心原則:協同式終止

絕不使用 TerminateThread。執行緒必須「自願」離開,透過檢查共享旗標或接收退出訊息主動返回/退出訊息迴圈。

一、Worker 執行緒 (無訊息迴圈)

標準模式:旗標輪詢 + AfxEndThread

// 執行緒參數結構
struct THREAD_PARAM {
    HWND hWndNotify;   // 通知主視窗用
    int  nCounter;
    BOOL bExitFlag;    // 終止旗標
};

UINT __cdecl WorkerProc(LPVOID pParam)
{
    THREAD_PARAM* p = (THREAD_PARAM*)pParam;
    while (!p->bExitFlag) {           // 1. 定期檢查旗標
        Sleep(100);                   // 模擬工作
        p->nCounter++;
        ::PostMessage(p->hWndNotify, WM_USER+1, 0, 0); // 通知 UI 更新
    }
    return 0;                         // 2. 正常返回 → 執行緒結束
}

主執行緒啟動與終止控制

// 啟動
m_pThread = AfxBeginThread(WorkerProc, &m_param,
    THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
m_pThread->m_bAutoDelete = FALSE;     // 手動管理生命週期
m_pThread->ResumeThread();

// 安全終止
void CMyDlg::OnStopThread()
{
    if (!m_pThread) return;
    m_param.bExitFlag = TRUE;         // 1. 設置旗標
    WaitForSingleObject(m_pThread->m_hThread, INFINITE); // 2. 等待結束
    delete m_pThread;                 // 3. 釋放 CWinThread 物件
    m_pThread = NULL;
}

二、UI 執行緒 (擁有訊息泵)

標準模式:PostThreadMessage(WM_QUIT) 或 PostQuitMessage

  • UI 執行緒在 CWinThread::Run() 內跑訊息迴圈,收到 WM_QUIT 即返回。
  • 外部終止:PostThreadMessage(pThread->m_nThreadID, WM_QUIT, 0, 0);
  • 內部終止:呼叫 PostQuitMessage(exitCode) 或 AfxEndThread(exitCode)。

AfxEndThread 內部機制 (THRDCORE.CPP)

void AFXAPI AfxEndThread(UINT nExitCode, BOOL bDelete)
{
    // 1. 從模組狀態移除 CWinThread 指標
    // 2. 清理 OLE/COM 資源
    // 3. bDelete 時 delete pThread
    // 4. AfxTermThread() 清理執行緒區域儲存 (TLS)
    // 5. _endthreadex(nExitCode) 真正結束 CRT 執行緒
}

注意:AfxEndThread 僅能在「該執行緒內部」呼叫,跨執行緒呼叫行為未定義。

三、完整範例:對話框專案 Demo

資源:IDD_DEMO_DIALOG

  • Static (IDC_STATIC) "資料:"
  • Edit (IDC_DATA) 顯示計數器
  • Button (IDC_BEGIN_THREAD) "啟動執行緒"
  • Button (IDC_END_THREAD) "終止執行緒"

關鍵代碼片段 (DemoDlg.h/.cpp)

// DemoDlg.h
#define WM_THREADMSG (WM_USER + 1)
struct THREAD_PARAM { HWND hWnd; int nData; BOOL bExit; };
class CDemoDlg : public CDialog {
    CWinThread* m_pThread;
    THREAD_PARAM m_param;
    afx_msg void OnBeginThread();
    afx_msg void OnEndThread();
    afx_msg LRESULT OnThreadMsg(WPARAM, LPARAM);
    DECLARE_MESSAGE_MAP()
};

// DemoDlg.cpp
UINT ThreadProc(LPVOID p) {
    auto* param = (THREAD_PARAM*)p;
    while (!param->bExit) {
        Sleep(100);
        param->nData++;
        ::PostMessage(param->hWnd, WM_THREADMSG, 0, 0);
    }
    return 0;
}

void CDemoDlg::OnBeginThread() {
    if (m_pThread) { AfxMessageBox(_T("已啟動")); return; }
    m_param = { m_hWnd, 0, FALSE };
    m_pThread = AfxBeginThread(ThreadProc, &m_param,
        THREAD_PRIORITY_ABOVE_NORMAL, 0, CREATE_SUSPENDED);
    m_pThread->m_bAutoDelete = FALSE;
    m_pThread->ResumeThread();
}

void CDemoDlg::OnEndThread() {
    if (!m_pThread) { AfxMessageBox(_T("未啟動")); return; }
    m_param.bExit = TRUE;
    WaitForSingleObject(m_pThread->m_hThread, INFINITE);
    delete m_pThread; m_pThread = NULL;
}

BEGIN_MESSAGE_MAP(CDemoDlg, CDialog)
    ON_MESSAGE(WM_THREADMSG, OnThreadMsg)
END_MESSAGE_MAP()

LRESULT CDemoDlg::OnThreadMsg(WPARAM, LPARAM) {
    SetDlgItemInt(IDC_DATA, m_param.nData);
    return 0;
}

四、進階議題與避坑指南

議題風險對策
旗標未聲明 volatile/atomic編譯器優化導致讀取舊值用 std::atomic<bool> 或 volatile BOOL (MSVC 保證不優化)
執行緒卡在阻塞 API (recv, WaitForSingleObject 無逾時)旗標永遠不被檢查改用非阻塞/逾時機制,或 CancelSynchronousIo / 關閉 Handle 強制喚醒
m_bAutoDelete = TRUE (預設)執行緒結束自動 delete CWinThread,主執行緒指標成懸空啟動後立即設 FALSE,手動管理
跨執行緒直接操作 UI 控制項競爭條件、Assert 失敗只能 PostMessage 通知 UI 執行緒由其更新
DLL 中使用 MFC 執行緒模組狀態混淆用 AFX_MANAGE_STATE、正確導出/導入

五、現代 C++ (C++11/17) 替代方案

新專案建議直接使用標準庫設施,避開 MFC 執行緒複雜性:

#include <thread>, <atomic>, <future>

std::atomic<bool> stopFlag{false};
std::thread worker([&]{
    while (!stopFlag) { /* work */ }
});

// 主執行緒
stopFlag = true;
worker.join(); // 等待結束,自動處理例外傳播
  • std::jthread (C++20) 內建 stop_token,更優雅。
  • 配合 std::condition_variable 實現高效等待/通知,避免 Sleep 輪詢浪費 CPU。

總結

MFC 執行緒安全終止的核心是 「旗標通知 + 主動退出 + 同步等待」。Worker 執行緒輪詢 bExit 旗標並 return;UI 執行緒接收 WM_QUIT 離開訊息泵。嚴禁 TerminateThread,妥善處理 m_bAutoDelete、阻塞呼叫、跨執行緒 UI 操作等細節,即可構建穩健的多執行緒 MFC 應用。現代專案優先考慮 std::thread/std::jthread 簡化架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved