做网站需要合同吗/网络营销策略的定义
本文仅是实验和实证的结果.(深层原因需要有机会了解ProcessMessages的实现)
1.缘起
Hotfox在移植到BCB环境下,以支持客户端开发后,在应用开发测试时发现:移植的本地管理模块在增加角色时会阻塞在一个简单的ShowMessage调用上.程序执行流程大致如下:
- 生成发送570-Request请求
- 执行同步调用,消息处理者为TFrmAddRole::Do572函数(CFormMsgHandler类型)
- 在TFrmAddRole::Do572函数中,处理成功提示时阻塞.(测试时在函数入口增加一行ShowMessage语句即导致阻塞)
2.解决思路
阻塞的原因是消息的处理是由后台线程调用的(bbox_c模块的HandleInput),且消息处理函数包含了VCL操作。目标是满足客户端开发要求,并且不想影响现有的应用代码。需要把后台线程的函数调用在框架层次转移到主线程上执行。
以下是对插件和TForm消息处理函数的转移执行的说明:.
对于CFormMsgHandler,
用
SendMessage(CBasePlugInModule::async_fc_wnd_,WM_ASYNC_FUNC_CALL,2,(LPARAM)new CFormFuncCallPara(form_,func_,msg->Duplicate()));
取代
(form_->*func_)(msg);
对于插件,用:
SendMessage(async_fc_wnd_,WM_ASYNC_FUNC_CALL,1,(LPARAM)new CPluginFuncCallPara(fp,p->mod_,in,&out,&or));
取代:
code = (p->mod_->*fp)(in, out,or);
(异步化一词来自最初的准备采用PostMessage的想法,已不合适,)
异步化函数调用消息由内部的TfrmAsyncFuncCall窗体处理.处理代码如下:
void __fastcall TfrmAsyncFuncCall::OnReceive(TMessage &Message)
{switch(Message.WParam) {case 1: {CPluginFuncCallPara *fcp = (CPluginFuncCallPara*)Message.LParam;fcp->Call();delete fcp;}break;case 2: {CFormFuncCallPara *fcp = (CFormFuncCallPara*)Message.LParam;fcp->Call();delete fcp;}break;case 3: {CShowMessagePara *fcp = (CShowMessagePara*)Message.LParam;///< @todo 外部指定如何显示信息,以适应不同的应用MessageShow(0,fcp->text_.c_str(),fcp->caption_.c_str(),MSGERROR);delete fcp;}break;}
}
迁移处理的相关定义如下:
#define WM_ASYNC_FUNC_CALL WM_USER+2///< AsyncFuncCall: 异步化函数调用
< WM_FUNC_CALL用于在多线程环境下把可能需要访问VCL对象的函数进行异步化处理的自定义窗口消息
///< WPARAM : 1-插件函数 2-Form函数 3-信息提示
///< LPARAM :根据WPARAM对应不同的结构体对象
//---------------------------------------------------------------------------
///< 插件函数异步调用消息参数
struct CPluginFuncCallPara {MSGFUNC fp_;CPlugInModule *mod_;CWrappedMsg<> *in_;vector<CWrappedMsg<> *> *out_;DISPATCH_RESULT *or_;CPluginFuncCallPara(MSGFUNC fp,CPlugInModule *mod,CWrappedMsg<> *in,vector<CWrappedMsg<> *> *out,DISPATCH_RESULT *or):fp_(fp),mod_(mod),in_(in),out_(out),or_(or) {}int Call() {return (mod_->*fp_)(in_, *out_,*or_);};
};//---------------------------------------------------------------------------
///< Form函数异步调用消息参数
struct CFormFuncCallPara {TForm *form_;FormMsgHandleFunc func_; ///< 消息处理函数CMsg *msg_;CFormFuncCallPara(TForm *form,FormMsgHandleFunc func,CMsg *msg):form_(form),func_(func),msg_(msg) {}int Call() {return (form_->*func_)(msg_);}
};
3.验证
测试发现修改后仍然阻塞,发生在以下位置(对于插件处理函数)SendMessage(async_fc_wnd_,WM_ASYNC_FUNC_CALL,1,(LPARAM)new CPluginFuncCallPara(fp,p->mod_,in,&out,&or));
没有进入到TfrmAsyncFuncCall::OnReceive中.
4.再究
测试发现由于同步调用导致:int ret = CBasePluginModule::sc_->call(msg,result,0,0,timeout);
同步调用发生在窗体上,主线程等待服务器的返回并处理后唤醒。
在处理返回的消息时SendMessage的消息没有机会被处理。
需要使用TApplication的ProcessMessage来中断当前操作,处理消息队列中的消息。
由于同步调用是另起一个线程(do_syncall_func)通过条件变量等待返回的消息被处理后被唤醒或超时或取消的。代码如下:
/// 执行同步调用
int HTX_Syncall::call(CMsg *msg,int &result,CMsg **ppmsg,SC_CALLBACK fp,int tv) {HANDLER_THREAD_INFO *ti = prepare(msg);SC_THREAD_PARA tp;tp.sc = this;tp.tv = tv;tp.result = 0;tp.thr_ = ACE_Thread::self();ACE_hthread_t hthread;ACE_Thread::spawn(do_syncall_func,&tp,THR_NEW_LWP|THR_JOINABLE,0,&hthread);{ACE_GUARD_RETURN(ACE_Thread_Mutex,guard,ti->lock_ready_cond_var_,-1);ti->ready_cond_var_->wait();///< 等待do_syncall_func进入wait}if (fp)(*fp)(msg);else {HTX_NETWORK::instance()->SendMsg(msg);}ACE_Thread::join(hthread);threads_.unbind(ti->cmd_id_); ///< 从等待队列中清除delete ti;result = tp.result;if (ppmsg) {*ppmsg = tp.ret_msg_;}else {if (tp.ret_msg_) tp.ret_msg_->Release();}return tp.wait_result;
}
在do_syncall_func函数中加入Application->ProcessMessage(通过回调cb_func_设置)后,仍然阻塞.
是否意味着,Application->ProcessMessage不能在非主线程中使用?
5.试验ProcessMessages
目的是确认在非主线程中执行ProcessMessage没有预期效果.试验方案:
主线程中创建2个线程,主线程组塞。
创建的2个线程,一个线程PostMessage或SendMessage,一个线程执行Applicaiton->ProcessMessage.
情形1:
DWORD __stdcall TestThreadProc(void *arg) {while(1) {Application->ProcessMessages(); Sleep(1000);};return 0;
}DWORD __stdcall TestThreadProc2(void *arg) {while(1) {SendMessage(form2->Handle,WM_ASYNC_FUNC_CALL,0,0); ///< 阻塞在此Sleep(30);}return 0;
}void __fastcall TForm1::Button3Click(TObject *Sender)
{CreateThread(0,0,TestThreadProc,0,0,0);CreateThread(0,0,TestThreadProc2,0,0,0);while(1) {Sleep(1000); };
}
情形2:正常
DWORD __stdcall TestThreadProc(void *arg) {while(1) {/// Application->ProcessMessages(); ///< 不论是否有此行都正常Sleep(1000);};return 0;
}DWORD __stdcall TestThreadProc2(void *arg) {while(1) {SendMessage(form2->Handle,WM_ASYNC_FUNC_CALL,0,0);Sleep(30);}return 0;
}void __fastcall TForm1::Button3Click(TObject *Sender)
{CreateThread(0,0,TestThreadProc,0,0,0);CreateThread(0,0,TestThreadProc2,0,0,0);while(1) {Application->ProcessMessages();Sleep(1000); };
}
结论:TApplication的ProcessMessages在非主线程中执行没有作用.
6.再试
修改同步调用函数,使ProcessMessages在主线程上执行。
代码修改成如下内容时,问题得以解决.
/// 执行同步调用
int HTX_Syncall::call(CMsg *msg,int &result,CMsg **ppmsg,SC_CALLBACK fp,int tv) {HANDLER_THREAD_INFO *ti = prepare(msg);SC_THREAD_PARA tp;tp.sc = this;tp.tv = tv;tp.result = 0;tp.thr_ = ACE_Thread::self();ACE_hthread_t hthread;ACE_Thread::spawn(do_syncall_func,&tp,THR_NEW_LWP|THR_JOINABLE,0,&hthread);{ACE_GUARD_RETURN(ACE_Thread_Mutex,guard,ti->lock_ready_cond_var_,-1);ti->ready_cond_var_->wait();///< 等待do_syncall_func进入wait}if (fp)(*fp)(msg);else {HTX_NETWORK::instance()->SendMsg(msg);}
#ifdef HTX_WINDOWS ///< 对于客户端程序,在主线程上的同步调用会导致窗口消息的阻塞.如果返回的消息在其它线程(如HTX_Scheduler任务线程)处理过程中,使用SeneMessage导致死锁///< 在此情况下,cb_func_的作用是处理消息队列中的消息(BORLANDC中执行Application->ProcessMessage)///< @note 在do_syncall_func函数中执行cb_func_没有效果,因为****Application->ProcessMessage不能在非主线程中执行****do {DWORD status = 0;if (!GetExitCodeThread((void*)hthread,&status))break;(*cb_func_)(0);if (status!=STILL_ACTIVE) {break;}Sleep(1000);} while(1);
#elseACE_Thread::join(hthread);
#endifthreads_.unbind(ti->cmd_id_); ///< 从等待队列中清除delete ti;result = tp.result;if (ppmsg) {*ppmsg = tp.ret_msg_;}else {if (tp.ret_msg_) tp.ret_msg_->Release();}return tp.wait_result;
}