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


本文仅是实验和实证的结果.(深层原因需要有机会了解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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值