1、什么是事件
首先,需要明确下什么叫事件。
事件,对应的英文是Event,表示有一件事情发生了。
如果一件事情,不与其他的对象相关联,发生时我们只要直接调用函数就可以了。就像上一节定时器实现一样,我们检测到定时器到期了,直接执行定时器的回调函数。
但如果涉及到两个对象,触发事件的一方,就需要一定的手段通知对方。
比如,你去朋友公司拜访他,朋友告诉你,你到公司门口,给我打电话,我下去接你。
这就是一个典型的事件场景:你到朋友公司门口,触发事件,打电话通知朋友,朋友得知你到公司门口,下楼来接你。
这个事件的通知机制,是你有朋友的电话号码,可以通过电话直接通知朋友。
那如果老板通知各部门经理,下午三点开会呢?
你应该已经想到了,直接在群里发个消息就行了,不用每个人打一个电话。
2、事件机制有什么用
上述的事件,虽然能够通知到相关角色,但通知发布的人,需要知道被通知人的联系方式,这样在程序开发中耦合太紧密了。
有时候,事件触发的对象,并不知道谁需要对事件做出响应,比如公司发出公告:下午2:00公司做核酸检测,今天没有做过核酸的同学到一楼大厅做核酸。
这个事件中,哪些人需要响应事件是需要结合自身情况处理的。而发布者是不关心具体谁要去执行,只需要将事件通知出去。
我们要实现的事件机制,就是用来解耦的。
我们可以通过一个发布订阅机制来实现事件通知。
首先有一个事件中心,各个对象实例可以到事件中心,注册自己关心的事件ID。
事件触发的模块,可以到事件中心发布事件,通知事件已经触发。
事件中心检索,是否有实例关心这个事件,如果有则通知相关实例。
这样,发布者就不需要了解谁关心这个事件,只需要将事件发布出去即可,由事件中心负责通知关心的实例。这样就将事件发布者和事件响应者解耦了。
3、事件机制实现
事件机制实现与上一节的定时器实现很相似,这里直接贴上代码分析
#pragma once
#include <functional>
#include <unordered_map>
#include <list>
#include <mutex>
class CHYEvent;
typedef std::function<void(CHYEvent*)> EventCBFunc;
typedef std::mutex Mutex;
typedef std::lock_guard<Mutex> AutoLock;
class CHYEvent
{
public:
bool Copy(CHYEvent& event){
nType = event.nType;
nDataLen = event.nDataLen;
if (nDataLen != 0)
{
memcpy(this+1, &event+1, nDataLen);
}
return true;
}
public:
int nType = 0;
int nDataLen = 0;
};
class EventMgr
{
private:
EventMgr();
public:
static EventMgr* Instance();
public:
bool RegisterEvent(int nEventType, void* owner, EventCBFunc func);
bool UnregisterEvent(int nEventType, void* owner);
bool PostEvent(CHYEvent* pEvent);
bool OnEvent(CHYEvent* pEvent);
private:
unsigned long WorkThreadFunc();
std::unordered_map<int, std::unordered_map<void*, EventCBFunc>> m_mapEvent;
Mutex m_lockEventMap;
std::list<CHYEvent*> m_listEvent;
Mutex m_lockEvent;
std::condition_variable m_cond;
};
Event只需定义两个变量,一个是Type表示事件类型,一个是DataLen记录后缀数据长度,用于Event类型扩展。
EventMgr有两个重要变量,m_mapEvent用来记录事件注册信息,收到事件后会在这个map中查询需要调用哪些回调。
m_listEvent记录当前的Event缓存队列,虽然Event都是立即处理的,但在多线程中,仍然可能出现处理不及时而积压的情况,因此需要一个容器存放缓存队列。
EventMgr启动一个工作线程(WorkThreadFunc),查询m_listEvent是否有数据,没有数据就等待,线程挂起。这里与定时器不同,定时器需要sleep一个间隔后再次运行,用于校验是否到期。
事件触发者可以调用PostEvent,向m_listEvent插入一个Event,并通过条件变量m_cond通知工作线程,事件队列中有数据了,需要工作线程进行处理。
工作线程取出事件,交给OnEvent函数处理,在m_mapEvent中查找回调函数并回调。
#include "HYEvent.h"
#include <thread>
EventMgr::EventMgr()
{
std::thread th(&EventMgr::WorkThreadFunc, this);
th.detach();
}
EventMgr* EventMgr::Instance()
{
static EventMgr instance;
return &instance;
}
bool EventMgr::RegisterEvent(int nEventType, void* owner, EventCBFunc func)
{
AutoLock lock(m_lockEventMap);
if ( m_mapEvent.count(nEventType) == 0 )
{
std::unordered_map<void*, EventCBFunc> map;
map[owner] = func;
m_mapEvent[nEventType] = map;
}
else
{
m_mapEvent[nEventType][owner] = func;
}
return true;
}
bool EventMgr::UnregisterEvent(int nEventType, void* owner)
{
AutoLock lock(m_lockEventMap);
if ( m_mapEvent.count(nEventType) == 0 ) return false;
if (m_mapEvent[nEventType].count(owner) == 0) return false;
m_mapEvent[nEventType].erase(owner);
if (m_mapEvent[nEventType].size() == 0)
{
m_mapEvent.erase(nEventType);
}
return true;
}
bool EventMgr::PostEvent(CHYEvent* pEvent)
{
int nSize = sizeof(CHYEvent) + pEvent->nDataLen;
char* t = new char[nSize];
CHYEvent* event = (CHYEvent*)t;
event->Copy(*pEvent);
std::unique_lock<std::mutex> locker(m_lockEvent);
m_listEvent.push_back(event);
locker.unlock();
m_cond.notify_one();
return true;
}
unsigned long EventMgr::WorkThreadFunc()
{
while(true)
{
std::unique_lock<std::mutex> locker(m_lockEvent);
while (m_listEvent.empty())
m_cond.wait(locker);
CHYEvent* pEvent = m_listEvent.front();
m_listEvent.pop_front();
locker.unlock();
OnEvent(pEvent);
}
return 0;
}
bool EventMgr::OnEvent(CHYEvent* pEvent)
{
AutoLock lock(m_lockEventMap);
if ( m_mapEvent.count(pEvent->nType) == 0 ) return false;
auto & map = m_mapEvent[pEvent->nType];
for (auto itr : map)
{
itr.second(pEvent);
}
char*e = (char*)pEvent;
delete []e;
return true;
}
4、调用方式
我们在ServiceBase中封装调用,方便派生服务使用
public:
virtual void PostEvent(CHYEvent* pEvent);
virtual void OnEvent(CHYEvent* pEvent);
void CServiceBase::PostEvent(CHYEvent* pEvent)
{
EventMgr::Instance()->PostEvent(pEvent);
}
void CServiceBase::OnEvent(CHYEvent* pEvent)
{
switch (pEvent->nType)
{
case 2:
LogInfo("this is Service event=s " );
break;
default:
break;
}
}
// 测试代码
EventMgr::Instance()->RegisterEvent(2, this, [&](CHYEvent* event){
LogInfo("Service On event %d", event->nType);
OnEvent(event);
});
CHYEvent event;
event.nType = 2;
EventMgr::Instance()->PostEvent(&event);
基类注册了监听事件(2),由基类的OnEvent函数处理。此后,在服务的任意位置触发这个事件,服务基类都可以收到事件信息,并进行处理。
为管理方便,需要有一个统一的头文件定义事件ID。