问题
某些MFC后台线程可能在主程序退出时仍然处于工作状态。主程序退出前,一般需要等待后台线程先退出。处理这类问题的机制通常是,主程序在OnClose()函数中通过激活事件通知后台线程退出。
在这种情况下,后台线程退出时调用SendMessage()函数发送消息,主程序已不能响应。因为,此时主程序进入OnClose()函数后,消息响应机制已经关闭。
使用WaitForSingleObject()函数的话是不能解决问题的,因为该函数不能够处理消息。
如何在这种情况下,让主线程继续处理后台线程发送出来的消息呢?
解决问题
这种情况下,我们可以使用微软提供的另一个函数MsgWaitForMultipleObjects()来解决这个问题。
请看下面这个例子。
1、在OnInitDialog()函数中,启动线程。重点来了:使用AfxBeginThread函数创建线程,线程退出时自动删除CWinThread对象m_hThread句柄。这样的话,MsgWaitForMultipleObjects函数就啥都等待不到了(退出机制不稳定,有时也能够等到)。所以,线程创建时要先挂起,后设置m_bAutoDelete为false。然后继续执行。
//该事件用于关闭线程
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
//开始线程,并挂起
m_pTaskThread = AfxBeginThread(Task_Thread, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, 0);
//关闭线程对象退出自动删除
m_pTaskThread->m_bAutoDelete = false;
//线程继续运行
m_pTaskThread->ResumeThread();
2、下面是后台线程的源代码。
//□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□//
//描述:后台任务线程
//参数:
//□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□//
UINT CThreadExitTestDlg::Task_Thread(LPVOID pParam)
{
CThreadExitTestDlg *pDlg;
bool bLoop;
DWORD dwResult;
int i;
CString str;
pDlg = (CThreadExitTestDlg *)pParam;
str.Format(_T("[Task] 线程开始!"));
::SendMessage(pDlg->GetSafeHwnd(), WM_SHOW_LOG, (WPARAM)&str, NULL);
bLoop = true;
i = 0;
while (bLoop)
{
dwResult = WaitForSingleObject(pDlg->m_hEvent, 1000);
switch (dwResult)
{
//退出线程
case WAIT_OBJECT_0:
bLoop = false;
break;
//超时,处理...
case WAIT_TIMEOUT:
str.Format(_T("[Task] %d!"), i ++);
::SendMessage(pDlg->GetSafeHwnd(), WM_SHOW_LOG, (WPARAM)&str, NULL);
break;
}
}
str.Format(_T("[Task] 线程结束!"));
::SendMessage(pDlg->GetSafeHwnd(), WM_SHOW_LOG, (WPARAM)&str, NULL);
return 0;
}
3、主程序在OnClose()函数关闭后台线程。
//□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□//
//描述:关闭程序
//参数:
//□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□//
void CThreadExitTestDlg::OnClose()
{
//激活事件,让后台线程退出
SetEvent(m_hEvent);
//关闭日志文件
m_cfLogFile.Close();
CDialogEx::OnClose();
}
4、如果激活事件后,主程序直接调用CDialogEx::OnClose()退出,那么
str.Format(_T("[Task] 线程结束!"));
::SendMessage(pDlg->GetSafeHwnd(), WM_SHOW_LOG, (WPARAM)&str, NULL);
上面这个消息,主程序肯定处理不到了。
5、如果一定要处理这个消息,怎么办呢?轮到MsgWaitForMultipleObjects()上场了。注意:退出前,手动删除线程对象。不然,内存溢出。
//□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□//
//描述:等待后台线程退出
//参数:
//□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□//
void CThreadExitTestDlg::WaitForTaskThreadExit(CWinThread *pThread)
{
DWORD dwResult;
MSG msg;
bool bLoop;
int i;
DWORD dwErrorCode;
bLoop = true;
while (bLoop)
{
if (pThread == NULL)
{
return;
}
dwResult = MsgWaitForMultipleObjects(1, &(pThread->m_hThread), FALSE, INFINITE, QS_SENDMESSAGE);
switch (dwResult)
{
case WAIT_OBJECT_0:
//手动删除线程对象
delete pThread;
bLoop = false;
break;
case WAIT_OBJECT_0 + 1:
//响应Windows消息
PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
TranslateMessage(&msg);
DispatchMessage(&msg);
break;
default:
dwErrorCode = GetLastError();
TRACE("dwError = %ld \r\n", dwErrorCode);
break;
}
}
}
6、把OnClose()函数改一下,如下。
//□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□//
//描述:关闭程序
//参数:
//□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□//
void CThreadExitTestDlg::OnClose()
{
//激活事件,让后台线程退出
SetEvent(m_hEvent);
//等待后台线程安全退出
WaitForTaskThreadExit(m_pTaskThread);
//关闭日志文件
m_cfLogFile.Close();
CDialogEx::OnClose();
}
WaitForTaskThreadExit()函数通过响应消息,恢复了消息泵直到后台线程退出。
效果
成功地处理了每一条消息,包括最后一条。完美!:)
代码下载:https://download.csdn.net/download/sunriver2000/12325602