You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 19:55:33