C++03环境下WinAPI实现重复检查线程致主窗口无响应问题
问题描述
使用Visual Studio 2010 SP1(C++03标准)开发MFC应用,主窗口为CMDIFrameWnd。通过CreateThread创建单独线程重复执行检查任务,检查失败时通过PostMessage通知UI线程弹出需用户操作的MessageBox。使用互斥量控制检查线程在MessageBox显示期间暂停,但运行一段时间后主窗口出现无响应情况,疑似存在逻辑错误或内存泄漏。
UI线程简化代码
// 存储重复检查所需参数的结构体 struct RepeatFunctionParameters { unsigned int repeatDelayInMilliseconds; HWND checkIsFailedPostMessageWindowHandler; UINT checkIsFailedPostMessageMessageId; HANDLE checkIsPausedMutexHandle; RepeatFunctionParameters(unsigned int _repeatDelayInMilliseconds, HWND _checkIsFailedPostMessageWindowHandler, UINT _checkIsFailedPostMessageMessageId, HANDLE _haspSerialCheckIsPausedMutexHandle) : repeatDelayInMilliseconds(_repeatDelayInMilliseconds), checkIsFailedPostMessageWindowHandler(_checkIsFailedPostMessageWindowHandler), checkIsFailedPostMessageMessageId(_checkIsFailedPostMessageMessageId), haspSerialCheckIsPausedMutexHandle(_haspSerialCheckIsPausedMutexHandle) // 错误:成员变量名拼写错误 {} }; // 创建互斥量,用于在UI显示MessageBox时暂停检查线程 HANDLE haspSerialCheckIsPausedMutexHandle = CreateMutex(NULL, FALSE, NULL); // 启动重复检查线程,间隔5000毫秒 RepeatFunctionParameters* params = new RepeatFunctionParameters(5000, myApp_hWnd, WM_USER_HASP_CHECK_FAILED, haspSerialCheckIsPausedMutexHandle); CreateThread(NULL, 0, RepeatFunction, params, 0, NULL); // 检查失败时发送的自定义消息 #define WM_USER_HASP_CHECK_FAILED (WM_USER+0x150) // 消息映射 ON_MESSAGE( WM_USER_HASP_CHECK_FAILED, OnUserHaspCheckFailed) // 消息处理函数声明 afx_msg LRESULT OnUserHaspCheckFailed(WPARAM wParam, LPARAM lParam); // 消息处理函数实现 LRESULT CMainWnd::OnUserHaspCheckFailed(WPARAM wParam, LPARAM lParam) { // 获取互斥量,暂停检查线程 WaitForSingleObject(haspSerialCheckIsPausedMutexHandle, INFINITE); // 显示需要用户操作的MessageBox if (::MessageBox(myApp_hWnd, _T("检查失败!重试还是取消?"), myApp_name, MB_RETRYCANCEL | MB_ICONERROR | MB_SYSTEMMODAL) == IDCANCEL) { // 用户点击取消则关闭主窗口 pWnd->SendMessage(WM_CLOSE, 0x00010000, 0); } // 释放互斥量,恢复检查线程 ReleaseMutex(haspSerialCheckIsPausedMutexHandle); return 0; } // WM_CLOSE消息处理 LRESULT CMainWnd::OnClose( WPARAM wParam, LPARAM lParam) { if( haspSerialCheckIsPausedMutexHandle != NULL) CloseHandle( haspSerialCheckIsPausedMutexHandle); CMDIFrameWnd::OnClose(); return 0; }
重复检查线程简化代码
DWORD WINAPI RepeatFunction(LPVOID parameters) { // 转换参数指针 RepeatFunctionParameters* temp = static_cast<RepeatFunctionParameters*>(parameters); // 拷贝参数到局部变量 RepeatFunctionParameters params = *temp; // 释放堆上的参数结构体 delete temp; // 无限循环执行检查 while (true) { // 等待互斥量(UI显示MessageBox时会持有互斥量,线程在此阻塞) WaitForSingleObject(params.haspSerialCheckIsPausedMutexHandle, INFINITE); // 错误:结构体中无此成员变量 // 立即释放互斥量 ReleaseMutex(params.haspSerialCheckIsPausedMutexHandle); // 错误:结构体中无此成员变量 DWORD startMilliseconds = GetTickCount(); // 执行检查任务 BOOL success = PerformACheck(); const unsigned long defaultSleepDelay = 1000; unsigned long sleepDelay; // 错误:未初始化 // 计算下次检查的间隔时间 DWORD endMilliseconds = GetTickCount(); if ((endMilliseconds - startMilliseconds) < params.repeatDelayInMilliseconds) sleepDelay = params.repeatDelayInMilliseconds - (endMilliseconds - startMilliseconds); // 检查失败时通知UI if (!success) { PostMessage(params.checkIsFailedPostMessageWindowHandler, params.checkIsFailedPostMessageMessageId, 0, 0); // 失败后间隔1000毫秒重试 sleepDelay = 1000; } Sleep(sleepDelay); } }
问题分析与解决办法
1. 致命错误:结构体成员变量名拼写错误
- 问题:
RepeatFunctionParameters结构体中定义的互斥量成员是checkIsPausedMutexHandle,但构造函数初始化列表中错误地写为haspSerialCheckIsPausedMutexHandle,导致该成员未被正确赋值;同时线程代码中错误地引用不存在的成员,实际操作的是未初始化的随机句柄,引发死锁、非法内存访问等未定义行为,直接导致主窗口无响应。 - 修复:统一变量名,修正构造函数和线程代码中的引用:
// 结构体构造函数修正 RepeatFunctionParameters(unsigned int _repeatDelayInMilliseconds, HWND _checkIsFailedPostMessageWindowHandler, UINT _checkIsFailedPostMessageMessageId, HANDLE _checkIsPausedMutexHandle) : repeatDelayInMilliseconds(_repeatDelayInMilliseconds), checkIsFailedPostMessageWindowHandler(_checkIsFailedPostMessageWindowHandler), checkIsFailedPostMessageMessageId(_checkIsFailedPostMessageMessageId), checkIsPausedMutexHandle(_checkIsPausedMutexHandle) // 修正为正确的成员名 {} // 线程代码中修正互斥量引用 WaitForSingleObject(params.checkIsPausedMutexHandle, INFINITE); ReleaseMutex(params.checkIsPausedMutexHandle);
2. 未初始化变量导致随机休眠时间
- 问题:线程中
sleepDelay变量未初始化,若检查任务耗时超过设定间隔,sleepDelay会保留随机值,可能导致线程休眠极长时间或无休眠,引发异常行为。 - 修复:初始化
sleepDelay为默认值:unsigned long sleepDelay = defaultSleepDelay; // 初始化默认值 DWORD endMilliseconds = GetTickCount(); if ((endMilliseconds - startMilliseconds) < params.repeatDelayInMilliseconds) sleepDelay = params.repeatDelayInMilliseconds - (endMilliseconds - startMilliseconds);
3. 线程无退出机制,主窗口关闭后仍运行
- 问题:线程使用
while(true)无限循环,主窗口关闭时线程不会自动退出,会继续占用资源,甚至向已销毁的窗口发送消息,引发错误。 - 修复:添加退出事件,主窗口关闭时触发事件,线程检测到事件后退出:
- 在
CMainWnd中添加成员变量:HANDLE m_hExitEvent;,初始化:m_hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - 修改参数结构体,添加事件句柄:
struct RepeatFunctionParameters { // ... 原有成员 HANDLE hExitEvent; RepeatFunctionParameters(..., HANDLE _hExitEvent) : // ... 原有初始化 hExitEvent(_hExitEvent) {} }; - 启动线程时传递事件句柄:
RepeatFunctionParameters* params = new RepeatFunctionParameters(5000, myApp_hWnd, WM_USER_HASP_CHECK_FAILED, haspSerialCheckIsPausedMutexHandle, m_hExitEvent); - 线程循环中检测事件:
while (true) { // 先检查是否需要退出 DWORD dwWait = WaitForSingleObject(params.hExitEvent, 0); if (dwWait == WAIT_OBJECT_0) break; // ... 原有检查逻辑 } - 主窗口关闭时设置事件并等待线程退出(需保存线程句柄):
LRESULT CMainWnd::OnClose( WPARAM wParam, LPARAM lParam) { SetEvent(m_hExitEvent); WaitForSingleObject(m_hThreadHandle, INFINITE); CloseHandle(m_hThreadHandle); CloseHandle(m_hExitEvent); CloseHandle(haspSerialCheckIsPausedMutexHandle); CMDIFrameWnd::OnClose(); return 0; }
- 在
4. 避免重复弹出MessageBox
- 问题:若检查连续失败,线程会多次发送
WM_USER_HASP_CHECK_FAILED消息到UI队列,导致用户关闭一个MessageBox后立即弹出下一个,影响体验。 - 修复:添加标记变量,控制MessageBox只显示一个:
- 在
CMainWnd中添加成员变量:bool m_bIsShowingErrorMsg;,初始化false - 修改消息处理函数:
LRESULT CMainWnd::OnUserHaspCheckFailed(WPARAM wParam, LPARAM lParam) { if (m_bIsShowingErrorMsg) return 0; m_bIsShowingErrorMsg = true; WaitForSingleObject(haspSerialCheckIsPausedMutexHandle, INFINITE); if (::MessageBox(myApp_hWnd, _T("检查失败!重试还是取消?"), myApp_name, MB_RETRYCANCEL | MB_ICONERROR | MB_SYSTEMMODAL) == IDCANCEL) { pWnd->SendMessage(WM_CLOSE, 0x00010000, 0); } ReleaseMutex(haspSerialCheckIsPausedMutexHandle); m_bIsShowingErrorMsg = false; return 0; }
- 在
5. 关闭线程句柄避免资源泄漏
- 问题:
CreateThread返回的线程句柄未关闭,导致资源泄漏。 - 修复:保存线程句柄,主窗口关闭时关闭:
- 在
CMainWnd中添加成员变量:HANDLE m_hThreadHandle; - 启动线程时保存句柄:
m_hThreadHandle = CreateThread(NULL, 0, RepeatFunction, params, 0, NULL); - 主窗口关闭时关闭句柄(见上述退出机制代码)。
- 在
内容的提问来源于stack exchange,提问作者bairog
相关产品推荐
相关产品推荐

