TApplicaiton.ProcessMessages不能在非主线程使用

来源:互联网 发布:python input split 编辑:程序博客网 时间:2024/06/08 07:05

本文仅是实验和实证的结果.(深层原因需要有机会了解ProcessMessages的实现)

1.缘起

Hotfox在移植到BCB环境下,以支持客户端开发后,在应用开发测试时发现:移植的本地管理模块在增加角色时会阻塞在一个简单的ShowMessage调用上.
程序执行流程大致如下:
  1. 生成发送570-Request请求
  2. 执行同步调用,消息处理者为TFrmAddRole::Do572函数(CFormMsgHandler类型)
  3. 在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);#else    ACE_Thread::join(hthread);#endif    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;}


原创粉丝点击