在《MFC环境下Start&Pause&Stop操作》一文中,执行背景任务的线程中有下面一段话:
::WaitForSingleObject(hSleepEvent, 2000);
即线程周期性执行任务中间,会有一个固定的时间间隔。这种情况也是常见的,比如工程中周期性对数据采样的时候。时间有长有短,短的时候,用上面的这种WaitFor是可行的;长的时候,尽管功能不会有什么问题,但用户体验会下降。
为了说明问题,把上面的2s改成20s:
::WaitForSingleObject(hSleepEvent, 20000);
然后试着在Start之后单击Stop,Stop之后立即拖动这个UI的标题栏、或者鼠标单击下面的编辑框,则会较大概率出现下面的无响应:(直接关闭窗口看不出该效果)
为方便说明问题原因,代码再贴一遍:
DWORD WINAPI CStartPauseStopDlg::ThreadProc(LPVOID lpThreadParameter)
{
CStartPauseStopDlg* pObj = (CStartPauseStopDlg*)lpThreadParameter;
HANDLE hSleepEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
for (;;) {
if (pObj->m_bStopped) {
//::AfxMessageBox("User stopped the task.");
break;
}
pObj->Doit();
::WaitForSingleObject(hSleepEvent, 20000);
}
return 0;
}
void CStartPauseStopDlg::OnBnClickedStop()
{
m_bStopped = TRUE;
GetDlgItem(IDC_START)->EnableWindow(TRUE);
GetDlgItem(IDC_START)->SetWindowText(_T("Start"));
GetDlgItem(IDC_PAUSE)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
::ResumeThread(m_hThread);
CloseThread();
m_dwCurrent = 0;
}
void CStartPauseStopDlg::CloseThread()
{
if (NULL == m_hThread) return;
m_bStopped = TRUE;
WaitForSingleObject(m_hThread, INFINITE);
CloseHandle(m_hThread);
m_hThread = NULL;
}
单击了Stop按钮之后,会执行OnBnClickedStop()函数,这个函数会唤醒执行线程,然后通过布尔变量m_bStopped让线程自己结束自己。而CloseThread()的WaitFor负责等待。线程真正结束之后,OnBnClickedStop()函数才返回。在这个过程中,拖动UI的标题栏,就会出现无响应。
如果唤醒线程后,线程刚好从for循环的下一句执行,立即判断出现要退出循环,则不会无响应。如果刚好碰到线程恢复后要执行周期性等待的WaitFor(类似于Sleep),则就无响应了。
如前面所述,如果这个等待时间过长,那么用户体验下降。
下面是一种解决方法,利用WaitForMultipleObject,即在原来Event的基础上,再增加一个Stop对应的Event。当用户选择Stop的时候,给该Event设置信号,WaitForMultipleObject可以立即退出。
示例代码:
在头文件中新增一个事件句柄:
HANDLE m_hStopEvent;
实现文件的部分代码(关注m_hStopEvent的部分):
BOOL CStartPauseStopDlg::OnInitDialog()
{
...
// TODO: Add extra initialization here
m_hStopEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
m_hThread = NULL;
m_bStopped = FALSE;
m_dwCurrent = 0;
GetDlgItem(IDC_PAUSE)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
}
void CStartPauseStopDlg::OnBnClickedStart()
{
m_bStopped = FALSE;
::ResetEvent(m_hStopEvent);
if (NULL == m_hThread) {
m_hThread = ::CreateThread(NULL, 0, ThreadProc, this, 0, NULL);
} else {
::ResumeThread(m_hThread);
}
GetDlgItem(IDC_START)->EnableWindow(FALSE);
GetDlgItem(IDC_PAUSE)->EnableWindow(TRUE);
GetDlgItem(IDC_STOP)->EnableWindow(TRUE);
}
void CStartPauseStopDlg::OnBnClickedStop()
{
m_bStopped = TRUE;
GetDlgItem(IDC_START)->EnableWindow(TRUE);
GetDlgItem(IDC_START)->SetWindowText(_T("Start"));
GetDlgItem(IDC_PAUSE)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
::SetEvent(m_hStopEvent);
::ResumeThread(m_hThread);
CloseThread();
m_dwCurrent = 0;
}
void CStartPauseStopDlg::OnDestroy()
{
CDialogEx::OnDestroy();
CloseThread();
::CloseHandle(m_hStopEvent);
}
DWORD WINAPI CStartPauseStopDlg::ThreadProc(LPVOID lpThreadParameter)
{
CStartPauseStopDlg* pObj = (CStartPauseStopDlg*)lpThreadParameter;
HANDLE hSleepEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE hEvents[2] = {hSleepEvent, pObj->m_hStopEvent};
for (;;) {
if (pObj->m_bStopped) {
//::AfxMessageBox("User stopped the task.");
break;
}
pObj->Doit();
::WaitForMultipleObjects(2, hEvents, FALSE, 20000);
}
return 0;
}
void CStartPauseStopDlg::CloseThread()
{
if (NULL == m_hThread) return;
m_bStopped = TRUE;
::SetEvent(m_hStopEvent);
WaitForSingleObject(m_hThread, INFINITE);
CloseHandle(m_hThread);
m_hThread = NULL;
}
后记:
以上代码没有CloseHandle(m_hStopEvent);找个地方安放它吧。