在一些应用程序中,除非用户去结束应用程序的运行,否则其中的子线程会一直处于运行状态。如果应用程序在结束时不主动通知子线程退出,有可能导致主线程结束后,子线程的系统资源得不到释放。如何通知子线程结束运行呢?这需要在程序设计阶段就考虑到,通常可以用事件、消息或全局变量来通知子线程退出运行循环或消息循环。消息通知方式适合用来通知有消息循环的用户界面线程退出,但理论上讲消息机制是不可靠的(消息可能会丢失,尽管可能性很小);事件通知的方式及时并且可靠,在应用程序准备结束运行前,设置一个通知事件;子线程的运行循环中不断检查通知事件,一旦发现事件信号设置,就可以退出循环,从而结束子线程的运行。主线程在设置通知事件后,不能马上退出,而应该等待子线程先结束后再退出。等待子线程结束方法很多,例如使用循环(while或 WM_CLOSE消息循环)检测线程状态等待,WaitForSingleObject函数等待,还有Sleep函数等待等,但有些方法在使用时可能导致线程死锁,因而必须小心。
举例来说,一个子线程不断将自己的处理进度刷新到对话框的进度条控件,这里刷新对话框事实上有赖于主线程的消息循环来处理刷新消息;如果用户不等处理结束就点击关闭对话框按钮,而主线程设置通知事件后如果使用WaitForSingleObject函数等待子线程结束的话,极可能导致线程死锁。因为 WaitForSingleObject函数会将主线程挂起(任何消息都得不到处理)直到子线程结束事件发生;而子线程可能正在设置进度条,一直在等待主线程将刷新消息处理完毕返回才会检测通知事件。于是,死锁发生了,应用程序"死"在那里无法自动结束,必须靠任务管理器将其强制结束(可能造成该程序的资源和内存得不到释放)。
采用循环检测的方法比较安全些,但while循环本身可能会导致主线程乃至子线程阻塞,子线程也因得不到及时调度而迟迟难以结束,应用程序界面可能半天都没有响应。当子程序结束需要较长时间时,这是很令人厌恶的。不过,可以通过在while循环中加入Sleep(10~50)主动释放CPU占用来促使子线程得到调度,也可以通过调高子线程的优先级来促使其尽快结束。而通过在WM_CLOSE消息处理函数中检测子线程状态的方法最值得推荐,该方法既不会挂起主线程,也不会阻塞子线程。检测如果发现子线程仍然在活动状态,就再此向主窗口发送一个WM_CLOSE消息,以便再次进入该消息处理函数检查子线程状态,直到子线程结束为止。
下面是等待线程结束的示例代码:
void CProgressDlg::OnClose() { DWORD dwStatus; if (m_pWorkerThread != NULL) { VERIFY(::GetExitCodeThread(m_pWorkerThread ->m_hThread, &dwStatus)); if (dwStatus == STILL_ACTIVE) { ::SetEvent(g_hEventKill); // 通知子线程结束运行 // 调高子线程优先级 m_pWorkerThread->SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL); PostMessage(WM_CLOSE); // 给本窗口再发一个结束消息 Return; } else { delete m_pWorkerThread; m_pWorkerThread = NULL; } } CDialog::OnClose(); } CDialog::OnClose(); }