在现代的软件开发中,多线程的使用变得日益普遍。尤其是在需要进行复杂计算或者需要保持用户界面响应性的情况下。虽然.NET框架提供了丰富的多线程支持,但在一些老旧的系统或者特定的应用场景中,仍然需要使用MFC(Microsoft Foundation Classes)来实现多线程。本文将介绍如何使用MFC中的CWinThread类来创建和管理UI线程,并实现UI线程与主线程之间的消息通信。
问题陈述 #1:
"创建一个UI线程,该线程每秒向主线程发送消息,消息内容是递增的计数器值。"
在Visual Studio中,可以通过向导快速创建MFC类,而不需要手动编写代码。以下是创建一个派生自CWinThread的类CCountingThread的步骤:
向导将生成以下框架代码:
class CCountingThread : public CWinThread
{
DECLARE_DYNCREATE(CCountingThread)
protected:
CCountingThread();
virtual ~CCountingThread();
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
protected:
DECLARE_MESSAGE_MAP()
};
接下来,需要在类中添加三个变量:
为了使主对话框能够每秒更新一次新的值,可以使用WM_TIMER消息,并在每个定时器消息上,向主窗口发送新值。以下是处理WM_TIMER消息的函数:
void CCountingThread::OnTimer(WPARAM wParam, LPARAM lParam)
{
m_pParentWnd->PostMessageW(WM_USER+2, 0, ++m_iCount);
}
为了使CCountingThread::OnTimer(...)函数能够处理WM_TIMER消息,需要在BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()之间添加以下代码:
ON_THREAD_MESSAGE(WM_TIMER, &CCountingThread::OnTimer)
接下来,需要在CCountingThread::InitInstance()中激活定时器,并在CCountingThread::ExitInstance()中停止定时器:
BOOL CCountingThread::InitInstance()
{
m_uTimerID = SetTimer(NULL, 2001, 1000, NULL);
return TRUE;
}
int CCountingThread::ExitInstance()
{
KillTimer(NULL, m_uTimerID);
return CWinThread::ExitInstance();
}
至此,已经完成了WinThread派生类的创建。接下来,需要设计主窗口UI,包括一个编辑框(用于显示来自线程的值)和两个按钮(用于启动和停止线程)。同时,需要在类中添加指向CCountingThread的指针。
以下是启动按钮处理程序的代码:
void CUserThread1Dlg::OnBnClickedStartThread()
{
if (m_pRunningThread == NULL)
{
m_pRunningThread = (CCountingThread*)AfxBeginThread(
RUNTIME_CLASS(CCountingThread),
0,
0,
CREATE_SUSPENDED,
NULL);
m_pRunningThread->m_pParentWnd = this;
m_pRunningThread->ResumeThread();
}
}
使用AfxBeginThread API以挂起模式创建UI线程,将CCountingThread的运行时类作为第一个参数,CREATE_SUSPENDED作为第四个参数。然后,将主窗口指针提供给m_pRunningThread->m_pParentWnd,并使用m_pRunningThread->ResumeThread()启动线程。
以下是停止UI线程的代码:
void CUserThread1Dlg::OnBnClickedStopThread()
{
if (m_pRunningThread != NULL)
{
m_pRunningThread->PostThreadMessageW(WM_QUIT, 0, 0);
m_pRunningThread = NULL;
}
}
关闭UI线程的最佳方式是向线程发送WM_QUIT消息,这将使线程的message-pump退出。
UI线程每秒发送WM_USER+2消息,更新计数器值,因此需要在对话框类中添加一个函数来处理它。以下是相关代码:
LRESULT CUserThread1Dlg::OnCountingIncrease(WPARAM wParam, LPARAM lParam)
{
CString strText;
strText.Format(_T("%d"), lParam);
m_edtCounting.SetWindowTextW(strText);
return LRESULT(0);
}
在BEGIN_MESSAGE_MAP()中添加消息监听器:
ON_MESSAGE(WM_USER+2, &CUserThread1Dlg::OnCountingIncrease)
编译并运行应用程序,查看其工作情况。
问题陈述 #2:
"添加一个重置计数器按钮,将计数器值重置为零。"
在主对话框中添加一个名为“重置计数器”的新按钮,并在代码中添加相应的处理程序。
以下是“重置计数器”按钮的OnClick处理程序代码:
void CUserThread1Dlg::OnBnClickedResetThreadcounter()
{
if (m_pRunningThread != NULL)
{
m_pRunningThread->PostThreadMessageW(WM_USER+1, 0, 0);
}
}
m_pRunningThread是CCountingThread类的对象,在点击“启动线程”按钮时创建。使用PostThreadMessageW,将消息发送给UI线程。
接下来,在CCountingThread类中添加一个函数来处理WM_USER+1用户消息:
void CCountingThread::ResetCounter(WPARAM wParam, LPARAM lParam)
{
m_iCount = 0;
}
在BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()之间添加以下消息监听器:
ON_THREAD_MESSAGE(WM_USER+1, &CCountingThread::ResetCounter)