MFC环境下Start&Pause&Stop操作(续):尽快结束任务

来源:互联网 发布:广州软件测试培训 编辑:程序博客网 时间:2024/06/05 17:36

在《MFC环境下Start&Pause&Stop操作》一文中,执行背景任务的线程中有下面一段话:

::WaitForSingleObject(hSleepEvent, 2000);

即线程周期性执行任务中间,会有一个固定的时间间隔。这种情况也是常见的,比如工程中周期性对数据采样的时候。时间有长有短,短的时候,用上面的这种WaitFor是可行的;长的时候,尽管功能不会有什么问题,但用户体验会下降。

为了说明问题,把上面的2s改成20s:

::WaitForSingleObject(hSleepEvent, 20000);

然后试着在Start之后单击Stop,Stop之后立即拖动这个UI的标题栏、或者鼠标单击下面的编辑框,则会较大概率出现下面的无响应:(直接关闭窗口看不出该效果)

no response

为方便说明问题原因,代码再贴一遍:

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);找个地方安放它吧。

0 0
原创粉丝点击