通知中心 SNotifyCenter¶
Warning
The current page still doesn't have a translation for this language.
You can read it through google translate.
在客户端开发中,我们经常需要在非 UI 线程中执行一些耗时操作(如网络请求、文件读写等),然后将结果通知给 UI 线程进行界面更新。SOUI 提供了 SNotifyCenter
通知中心来优雅地处理这类异步通知需求。
SNotifyCenter 通知中心¶
为了解决传统方法的局限性,SOUI 引入了 SNotifyCenter
通知中心,专门用于处理跨线程通知,可以通过SApplication对象来启用或者禁用通知中心。
核心优势¶
- 全局访问:通过单例模式,可以在代码的任意位置获取通知中心指针
- 灵活处理:采用 SOUI 的事件系统进行事件派发,结合事件订阅机制,可以在任意位置处理发出的事件
核心接口¶
/**
* @class SNotifyCenter
* @brief 通知中心类,管理事件的注册、注销和触发
*/
class SOUI_EXP SNotifyCenter
: public INotifyCenter
, public SSingleton2<SNotifyCenter>
, public SEventSet
, protected INotifyCallback {
SINGLETON2_TYPE(SINGLETON_NOTIFYCENTER)
friend SApplication;
private:
/**
* @brief 构造函数
* @param nIntervel 事件处理间隔时间(毫秒)
*/
SNotifyCenter(int nIntervel = 20);
/**
* @brief 析构函数
*/
~SNotifyCenter(void);
public:
/**
* @brief 触发一个同步通知事件
* @param e 事件参数对象
*
* @details 只能在UI线程中调用
*/
STDMETHOD_(void, FireEventSync)(THIS_ IEvtArgs *e) OVERRIDE;
/**
* @brief 触发一个异步通知事件
* @param e 事件参数对象
*
* @details 可以在非UI线程中调用,EventArgs *e必须是从堆上分配的内存,调用后使用Release释放引用计数
*/
STDMETHOD_(void, FireEventAsync)(THIS_ IEvtArgs *e) OVERRIDE;
/**
* @brief 注册一个处理通知的对象
* @param slot 事件处理对象
* @return 成功返回TRUE,失败返回FALSE
*/
STDMETHOD_(BOOL, RegisterEventMap)(THIS_ const IEvtSlot *slot) OVERRIDE;
/**
* @brief 注销一个处理通知的对象
* @param slot 事件处理对象
* @return 成功返回TRUE,失败返回FALSE
*/
STDMETHOD_(BOOL, UnregisterEventMap)(THIS_ const IEvtSlot *slot) OVERRIDE;
/**
* @brief 在UI线程中运行一个可运行对象
* @param pRunnable 可运行对象
* @param bSync 同步执行标志
*/
STDMETHOD_(void, RunOnUI)(THIS_ IRunnable *pRunnable, BOOL bSync) OVERRIDE;
/**
* @brief 在UI线程中运行一个函数
* @param fun 函数指针
* @param wp WPARAM参数
* @param lp LPARAM参数
* @param bSync 同步执行标志
*/
STDMETHOD_(void, RunOnUI2)(THIS_ FunRunOnUI fun, WPARAM wp, LPARAM lp, BOOL bSync) OVERRIDE;
public:
#ifdef ENABLE_RUNONUI
/**
* @brief 在UI线程中同步运行一个闭包
* @param fn 闭包函数
*/
void RunOnUISync(std::function<void(void)> fn);
/**
* @brief 在UI线程中异步运行一个闭包
* @param fn 闭包函数
*/
void RunOnUIAsync(std::function<void(void)> fn);
#endif
protected:
/**
* @brief 触发事件
* @param e 事件参数对象
*/
virtual void OnFireEvent(IEvtArgs *e);
/**
* @brief 触发多个事件
*/
virtual void OnFireEvts();
tid_t m_dwMainTrdID; // 主线程ID
SList<IEvtSlot *> m_evtHandlerMap; // 事件处理对象列表
SNotifyReceiver *m_pReceiver; // 通知接收器
SCriticalSection m_cs; // 临界区对象
SList<IEvtArgs *> m_ayncEvent; // 异步事件列表
BOOL m_bRunning; // 运行状态标志
int m_nInterval; // 事件处理间隔时间(毫秒)
SList<SAutoRefPtr<IRunnable> > m_asyncRunnable; // 异步可运行对象列表
};
自动事件映射注册模板¶
template<class T>
class TAutoEventMapReg
{
typedef TAutoEventMapReg<T> _thisClass;
public:
TAutoEventMapReg()
{
SNotifyCenter::getSingleton().RegisterEventMap(Subscriber(&_thisClass::OnEvent,this));
}
~TAutoEventMapReg()
{
SNotifyCenter::getSingleton().UnregisterEventMap(Subscriber(&_thisClass::OnEvent,this));
}
protected:
bool OnEvent(EventArgs *e){
T * pThis = static_cast<T*>(this);
return !!pThis->_HandleEvent(e);
}
};
使用步骤¶
1. 启用通知中心¶
DECLARE_INTERFACE_(IApplication, IObjRef){
STDMETHOD_(void, EnableNotifyCenter)(THIS_ BOOL bEnable, int interval DEF_VAL(20)) PURE;
};
int main(){
SApplication app;
// 启用通知中心
app.EnableNotifyCenter(TRUE);
app.Run();
return 0;
}
2. 定义事件类型¶
首先定义需要通过通知中心分发的事件类型:
// 定义自定义事件
class EventThreadStart : public TplEventArgs<EventThreadStart>
{
SOUI_CLASS_NAME(EventThreadStart, L"on_thread_start")
public:
EventThreadStart(SWindow *pSender) : TplEventArgs<EventThreadStart>(pSender) {}
enum { EventID = EVT_EXTERNAL_BEGIN + 1000 };
};
class EventThreadStop : public TplEventArgs<EventThreadStop>
{
SOUI_CLASS_NAME(EventThreadStop, L"on_thread_stop")
public:
EventThreadStop(SWindow *pSender) : TplEventArgs<EventThreadStop>(pSender) {}
enum { EventID = EVT_EXTERNAL_BEGIN + 1001 };
};
class EventThread : public TplEventArgs<EventThread>
{
SOUI_CLASS_NAME(EventThread, L"on_thread")
public:
EventThread(SWindow *pSender) : TplEventArgs<EventThread>(pSender) {}
enum { EventID = EVT_EXTERNAL_BEGIN + 1002 };
};
注册事件类型:
void CMainDlg::OnBtnStartNotifyThread()
{
if(IsRunning()) return;
// 注册事件类型
SNotifyCenter::getSingleton().addEvent(EVENTID(EventThreadStart));
SNotifyCenter::getSingleton().addEvent(EVENTID(EventThreadStop));
SNotifyCenter::getSingleton().addEvent(EVENTID(EventThread));
// 触发同步事件
EventThreadStart evt(this);
SNotifyCenter::getSingleton().FireEventSync(&evt);
BeginThread();
}
void CMainDlg::OnBtnStopNotifyThread()
{
if(!IsRunning()) return;
EndThread();
// 触发同步事件
EventThreadStop evt(this);
SNotifyCenter::getSingleton().FireEventSync(&evt);
// 移除事件类型
SNotifyCenter::getSingleton().removeEvent(EventThreadStart::EventID);
SNotifyCenter::getSingleton().removeEvent(EventThreadStop::EventID);
SNotifyCenter::getSingleton().removeEvent(EventThread::EventID);
}
3. 订阅事件处理¶
让需要处理通知中心分发事件的类从 TAutoEventMapReg
继承,实现事件的自动订阅:
class CMainDlg : public SHostDialog
, public TAutoEventMapReg<CMainDlg> // 继承自动事件映射注册
{
// ...
};
4. 处理异步事件¶
在事件映射表中处理事件:
// 事件处理映射表
BEGIN_MSG_MAP_EX(CMainDlg)
// ...
END_MSG_MAP()
// 事件处理函数
BOOL CMainDlg::_HandleEvent(EventArgs *pEvt)
{
if (pEvt->id == EventThread::EventID)
{
EventThread *pThreadEvt = (EventThread*)pEvt;
// 处理线程事件
SStringT strMsg;
strMsg.Format(_T("线程消息: %d"), GetTickCount());
m_pOutput->SetWindowText(strMsg);
return TRUE;
}
else if (pEvt->id == EventThreadStart::EventID)
{
EventThreadStart *pStartEvt = (EventThreadStart*)pEvt;
// 处理线程开始事件
m_pOutput->SetWindowText(_T("线程已启动"));
return TRUE;
}
else if (pEvt->id == EventThreadStop::EventID)
{
EventThreadStop *pStopEvt = (EventThreadStop*)pEvt;
// 处理线程停止事件
m_pOutput->SetWindowText(_T("线程已停止"));
return TRUE;
}
return FALSE;
}
5. 在工作线程中触发异步事件¶
// 工作线程函数
static DWORD WINAPI ThreadProc(LPVOID lpParam)
{
CMainDlg *pMainDlg = (CMainDlg*)lpParam;
while(pMainDlg->IsRunning())
{
// 创建事件对象(必须在堆上分配)
EventThread *pEvt = new EventThread(pMainDlg);
// 触发异步事件(可在非UI线程中调用)
SNotifyCenter::getSingleton().FireEventAsync(pEvt);
// 释放事件对象引用
pEvt->Release();
// 休眠一段时间
Sleep(1000);
}
return 0;
}
最佳实践¶
-
事件对象生命周期:使用
FireEventAsync
时,确保事件对象是从堆上分配的,并在调用后调用Release()
释放引用计数 -
事件类型管理:合理注册和注销事件类型,避免事件ID冲突
-
线程安全:
FireEventSync
只能在 UI 线程中调用,而FireEventAsync
可以在任意线程中调用 -
自动注册:使用
TAutoEventMapReg
模板类可以实现事件处理的自动注册和注销
注意事项¶
- 确保在应用程序初始化时创建
SNotifyCenter
单例 - 异步事件处理依赖于 UI 线程的消息循环
- 及时注销不再需要的事件类型和事件处理器,避免内存泄漏
具体用法请参考 SOUI 的官方 demo 示例。