考虑这样一个需求:当客户在终端数据导入界面选择需要导入的文件,点击导入后,终端会通过FTP上传导入的数据,如果上传文件很大很多,耗时很长,客户感觉会非常不好。主要是因为UI线程被阻塞,不再响应窗口消息,给客户的感觉终端像死掉一样,而客户也不知道上传文件的进度,所以希望终端提供进度条来显示上传的进度。
这是一个用户界面编程的经典问题,这个问题的一种解决方案是使用多线程,通过把FTP上传导入的工作委派给一个工作线程来处理,UI线程在调用函数启动工作线程后,自动返回。
工作线程在运行过程中不断向UI线程发送更新消息,UI线程把工作线程的工作进度显示在界面上,这样进度条的显示将是一个真实的工作进度,而不是一个虚假的进度。当工作线程完成任务后,会向UI线程发送一个结束任务的消息,UI线程在收到消息后,进行一些收尾工作。
因为这个问题是一个通用问题,我们考虑设计一个工作线程的应用程序框架。我们的设计目标是把工作线程的启动,结束和终止操作,以及工作线程与UI线程的通讯等繁琐操作封装在应用程序框架中,而把线程实现的具体业务逻辑与之分类开来,让工作线程的代码尽量的得到复用。让应用程序框架的使用者把精力主要集中于具体业务逻辑的实现。
具体的实现我采用了Adapter模式,使用其中的组合实现方式,并且运用了Policy技术(Modern C++ Design所提出的泛型设计技术)。
下图是类结构图:
这样,在CWorkThread模板类中的svc函数中,会在循环中调用函数对象的operator()运算符,因此用户只要实现一个包括operator()运算符的类,处理具体的业务就可以了。
当然用户还需要编写代码,处理工作线程为进度条和窗口所发的消息,进行相应的界面更新处理,和线程结束处理工作。
通过CWorkThread模板类把线程类CWorkThreadBase类和函数对象的工作结合起来,CWorkThread模板类实际上是一个适配器,这样就实现了工作线程框架。
具体代码如下:
const UINT WM_USER_THREAD_UPDATE = WM_USER+0x101; //线程进度更新消息
const UINT WM_USER_THREAD_FINISHED = WM_USER+0x102; //线程完成消息
const UINT WM_USER_THREAD_ABORTED = WM_USER+0x103; //线程废止消息
class CWorkThreadBase
{
public:
CWorkThreadBase(HWND hWnd = NULL);
virtual ~CWorkThreadBase(void);
protected:
HWND m_hWnd;//接收消息的窗口的句柄,句柄在线程间传递是安全的
BOOL m_bWorking;//线程是否工作
HANDLE m_hWorkThread; // 工作线程句柄。
HANDLE m_hEvent;
public:
int BeginThread(string& sError);
void KillThread(void);
int ThreadFinished(void);
bool IsWork(void){return m_bWorking;}
private:
static unsigned __stdcall WorkThreadFunc(void* pParam);
virtual int svc() = 0;
};
int GetOSError(string& sError);
CWorkThreadBase::CWorkThreadBase(HWND hWnd)
:m_hWnd(hWnd)
{
m_bWorking = FALSE;
m_hWorkThread = NULL;
m_hEvent = CreateEvent(NULL,
TRUE,//手工reset
FALSE, //未通知状态
NULL);
}
CWorkThreadBase::~CWorkThreadBase(void)
{
}
int CWorkThreadBase::BeginThread(string& sError)
{
if(m_bWorking)
{
sError = "The thread is still working!";
return OP_FAULT;
}
m_bWorking = TRUE;
ResetEvent (m_hEvent);
unsigned uThreadID;
//启动工作线程,调用WorkThreadFunc函数
unsigned long res = _beginthreadex (
NULL,// security
0,//stack_size
CWorkThreadBase::WorkThreadFunc,
this,
CREATE_SUSPENDED,
&uThreadID);
if(res == 0)
{
GetOSError(sError);
return OP_FAULT;
}
m_hWorkThread = OpenThread(SYNCHRONIZE | THREAD_SUSPEND_RESUME,
FALSE,
uThreadID);
if(m_hWorkThread == NULL)
{
GetOSError(sError);
return OP_FAULT;
}
int rv = ResumeThread (m_hWorkThread);
if(rv <0)
{
GetOSError(sError);
return OP_FAULT;
}
else if(rv >1)
{
sError = "The thread is still suspended!";
return rv;
}
return OP_SUCCESS;
}
void CWorkThreadBase::KillThread(void)
{
//杀死线程
if(m_bWorking)
{
SetEvent(m_hEvent);
}
}
int CWorkThreadBase::ThreadFinished(void)
{
if(m_hWorkThread != NULL)
{
//等待线程彻底停止
::WaitForSingleObject(m_hWorkThread, INFINITE);
CloseHandle (m_hWorkThread);
}
CloseHandle (m_hEvent);
m_hWorkThread = NULL;
m_bWorking = FALSE;//注意,只有在线程彻底停止后才设置FALSE
return OP_SUCCESS;
}
unsigned __stdcall CWorkThreadBase::WorkThreadFunc(void* pParam)
{
CWorkThreadBase* pThread = (CWorkThreadBase*)pParam;
//接收消息的窗口句柄
HWND hWnd = pThread->m_hWnd;
int rv = 0 ;
try
{
rv = pThread->svc();
}
catch(...)
{
rv = OP_FAULT;
}
bool bAorted = (WaitForSingleObject(pThread->m_hEvent, 0)
== WAIT_OBJECT_0);
//发消息通知消息窗口,线程已经结束
if(IsWindow(hWnd) && !bAorted)
{
::PostMessage(hWnd, WM_USER_THREAD_FINISHED,(WPARAM)rv,(LPARAM)pThread);
}
return (unsigned) rv;
}
int GetOSError(string& sError)
{
DWORD dwError = ::GetLastError();
HLOCAL hlocal = NULL; // Buffer that gets the error message string
BOOL bResult = ::FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, dwError, 0, (PTSTR) &hlocal, 0, NULL);
if (!bResult)
{
// Is it a network-related error?
HMODULE hDll = ::LoadLibraryEx(TEXT("netmsg.dll"), NULL,
DONT_RESOLVE_DLL_REFERENCES);
if (hDll != NULL)
{
::FormatMessage(
FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM,
hDll, dwError, 0, (PTSTR) &hlocal, 0, NULL);
::FreeLibrary(hDll);
}
}
if (hlocal != NULL)
{
sError = (PCTSTR) ::LocalLock(hlocal);
::LocalFree(hlocal);
return OP_SUCCESS;
}
else
{
return OP_FAULT;
}
}
template <class Functor,
class Container = vector<string> >
class CWorkThread : public CWorkThreadBase
{
public:
CWorkThread(const Functor& func,
HWND hwnd = NULL,
HWND hStatus = NULL)
:CWorkThreadBase(hwnd),
func_(func),
hStatusWnd_(hStatus)
{}
virtual ~CWorkThread(void)
{}
void refreshContainer(const Container& cont)
{
cont_.clear();
cont_.insert(cont_.begin(),cont.begin(),cont.end());
}
protected:
virtual int svc()
{
int rv = 0;
size_t iTotalNums = cont_.size();
Container::const_iterator cit = cont_.begin();
for(size_t iCurrentNums =1;
cit != cont_.end(); ++cit,++iCurrentNums)
{
string sUpdateInfo;
rv = func_(*cit,sUpdateInfo);
if(rv != 0)
{
break;
}
//如果事件对象被触发,则终止线程
if(WaitForSingleObject (m_hEvent, 0) == WAIT_OBJECT_0
&& IsWindow(m_hWnd))
{
//发消息通知窗口,线程已经终止
::PostMessage(m_hWnd, WM_USER_THREAD_ABORTED,
(WPARAM)-1,(LPARAM)this);
return -1;
}
//发消息通知更新进度条的显示
if(IsWindow(hStatusWnd_))
{
::SendMessage(hStatusWnd_, WM_USER_THREAD_UPDATE,
(WPARAM)iCurrentNums,
(LPARAM)iTotalNums);
}
if(!sUpdateInfo.empty())
{
updateInfo(sUpdateInfo);//用于客户显示额外的更新信息
}
}
return rv;
}
virtual void updateInfo (const string& sUpdateInfo)
{
if(IsWindow(m_hWnd))
{
SendMessage(m_hWnd, WM_USER_THREAD_UPDATE,
(WPARAM)sUpdateInfo.c_str(),
(LPARAM)0);
}
}
private:
Container cont_;
Functor func_;
HWND hStatusWnd_;//进度条的窗口句柄
};
参考文献:
1 《设计模式-可复用面向对象软件的基础》(Design Patterns - Elements of Reusable Object-Oriented Software)
2 《C++设计新思维——泛型编程与设计模式之应用》(Modern C++ Design ——Generic Programming and Design Patterns Applied)
3 《Windows核心编程》