第三十四篇:在SOUI中使用异步通知
概述
异步通知是客户端开发中常见的需求,比如在一个网络处理线程中要通知UI线程更新等等。
通常在Windows编程中,为了方便,我们一般会向UI线程的窗口句柄Post/Send一个窗口消息从而达到将非UI线程的事件切换到UI线程处理的目的。
在SOUI引入通知中心以前要在SOUI中处理非UI线程事件我也推荐用上面的方法。
使用窗口消息至少有以下两个不足:
1、需要在线程中持有一个窗口句柄。
2、发出的消息只能在该窗口句柄的消息处理函数里处理。
SNotifyCenter
最新的SOUI引入了一个新的单例对象:SNotifyCenter,专门用来处理这类问题。
新的SNotifyCenter解决了窗口消息存在的上面的两个问题:
1、通过使用全局单例,SNotifyCenter可以在代码任意位置获取它的指针(前提当然是要在它的生命周期内);
2、使用SNotifyCenter产生的通知采用SOUI的事件系统来派发,通过结合SOUI的事件订阅系统,用户可以在任意位置处理发出的事件。
在介绍如何使用SNotifyCenter前,先看一下NotifyCenter.h的代码:
1 #pragma once 2 3 #include <core/SSingleton.h> 4 5 namespace SOUI 6 { 7 template<class T> 8 class TAutoEventMapReg 9 { 10 typedef TAutoEventMapReg<T> _thisClass; 11 public: 12 TAutoEventMapReg() 13 { 14 SNotifyCenter::getSingleton().RegisterEventMap(Subscriber(&_thisClass::OnEvent,this)); 15 } 16 17 ~TAutoEventMapReg() 18 { 19 SNotifyCenter::getSingleton().UnregisterEventMap(Subscriber(&_thisClass::OnEvent,this)); 20 } 21 22 protected: 23 bool OnEvent(EventArgs *e){ 24 T * pThis = static_cast<T*>(this); 25 return !!pThis->_HandleEvent(e); 26 } 27 }; 28 29 class SOUI_EXP SNotifyCenter : public SSingleton<SNotifyCenter> 30 , public SEventSet 31 { 32 public: 33 SNotifyCenter(void); 34 ~SNotifyCenter(void); 35 36 /** 37 * FireEventSync 38 * @brief 触发一个同步通知事件 39 * @param EventArgs *e -- 事件对象 40 * @return 41 * 42 * Describe 只能在UI线程中调用 43 */ 44 void FireEventSync(EventArgs *e); 45 46 /** 47 * FireEventAsync 48 * @brief 触发一个异步通知事件 49 * @param EventArgs *e -- 事件对象 50 * @return 51 * 52 * Describe 可以在非UI线程中调用,EventArgs *e必须是从堆上分配的内存,调用后使用Release释放引用计数 53 */ 54 void FireEventAsync(EventArgs *e); 55 56 57 /** 58 * RegisterEventMap 59 * @brief 注册一个处理通知的对象 60 * @param const ISlotFunctor &slot -- 事件处理对象 61 * @return 62 * 63 * Describe 64 */ 65 bool RegisterEventMap(const ISlotFunctor &slot); 66 67 /** 68 * RegisterEventMap 69 * @brief 注销一个处理通知的对象 70 * @param const ISlotFunctor &slot -- 事件处理对象 71 * @return 72 * 73 * Describe 74 */ 75 bool UnregisterEventMap(const ISlotFunctor & slot); 76 protected: 77 void OnFireEvent(EventArgs *e); 78 79 void ExecutePendingEvents(); 80 81 static VOID CALLBACK OnTimer( HWND hwnd, 82 UINT uMsg, 83 UINT_PTR idEvent, 84 DWORD dwTime 85 ); 86 87 SCriticalSection m_cs; //线程同步对象 88 SList<EventArgs*> *m_evtPending;//挂起的等待执行的事件 89 DWORD m_dwMainTrdID;//主线程ID 90 91 UINT_PTR m_timerID; //定时器ID,用来执行异步事件 92 93 SList<ISlotFunctor*> m_evtHandlerMap; 94 }; 95 }
在这个文件中提供了两个类,一个就是SNotifyCenter,另一个是TAutoEventMapReg<T>。
可以看到SNotifyCenter中除构造外只有4个public方法:
FireEventSync, FireEventAsync用来触发事件。
RegisterEventMap,UnregisterEventMap则用来提供事件处理订阅。
如何使用SNotifyCenter?
1、在main中实例化SNotifyCenter。(不明白可以参考demo)
2、定义需要通过通知中心分发的事件类型,首先定义事件,然后向通知中心注册,参见下面代码:
1 void CMainDlg::OnBtnStartNotifyThread() 2 { 3 if(IsRunning()) return; 4 SNotifyCenter::getSingleton().addEvent(EVENTID(EventThreadStart)); 5 SNotifyCenter::getSingleton().addEvent(EVENTID(EventThreadStop)); 6 SNotifyCenter::getSingleton().addEvent(EVENTID(EventThread)); 7 8 EventThreadStart evt(this); 9 SNotifyCenter::getSingleton().FireEventSync(&evt); 10 BeginThread(); 11 } 12 13 void CMainDlg::OnBtnStopNotifyThread() 14 { 15 if(!IsRunning()) return; 16 17 EndThread(); 18 EventThreadStop evt(this); 19 SNotifyCenter::getSingleton().FireEventSync(&evt); 20 21 SNotifyCenter::getSingleton().removeEvent(EventThreadStart::EventID); 22 SNotifyCenter::getSingleton().removeEvent(EventThreadStop::EventID); 23 SNotifyCenter::getSingleton().removeEvent(EventThread::EventID); 24 }
3、使需要处理通知中心分发的事件的对象从TAutoEventMapReg继承,实现事件的自动订阅(方便在事件映射表中统一处理事件),这一步是可选的,你也可以直接使用SOUI提供的事件订阅机制向通知中心订阅特定事件。
4、在事件映射表里处理事件(没有第3步时,则同样没有这一步)。
具体用法参见SOUI的demo。