跨模块调用是一个比较头疼的问题,往往因为模块耦合性比较强,使得代码逻辑比较复杂。
1. 如何能实现跨模块调用,又不互相耦合?
通知者不关心谁需要处理该消息,处理者也不一定需要知道是谁发送了此消息。
windows自带的消息机制刚好可以解决该问题,但是此消息机制有几个致命的缺点:一,必须有窗口支持,当然该部分逻辑可以注册一个虚拟窗口来处理,但是也为编码带来了很大阻碍。二,更致命的是,消息队列在资源紧缺的情况下,还会出现消息丢失的情况。
但是PostMessage这个思维还是值得推广的。
2. 参数如何传递?
传统思想就是通过传递void*来传递,但是这个办法有个两个比较恶心的地方:一,类型不安全。 二,需要自己手动释放该指针。C++17引入了any,完美解决了以上两个问题,也为泛型编程提供了很多便利。
以下就是代码实现的思路:
1. MessageService提供了注册,以及PostMessage功能,是业务功能的核心支持。
2. IMsgBus是所有接收者的基类,接收者需要派生该类来实现消息接收。
3. 考虑到有某些情况,发送者发送消息时,需要通知的模块可能还未初始化,使得接收可能会丢失这条消息。针对该情况可以在MessageService里面提供一个缓存功能。(该功能还未实现)
下面贴上代码实现:
#include <windows.h>
#include <iostream>
#include <any>
#include <functional>
#include <string>
#include <mutex>
#include <queue>
class IMsgBus
{
public:
virtual ~IMsgBus() {}
virtual void Recv(std::any) = 0;
};
using MsgBusPtr = std::shared_ptr<IMsgBus>;
class MessageService
{
public:
~MessageService()
{
_message_loop.detach();
_stop = true;
for (auto recv : _queue_recv)
{
recv.second.clear();
}
_queue_recv.clear();
_queue_send.empty();
}
static MessageService& Instance()
{
static std::once_flag s_flag;
std::call_once(s_flag, [&]() {
_instance.reset(new MessageService);
});
return *_instance;
}
void PostMessage(std::any message)
{
std::unique_lock<std::mutex> lock(_lock_send);
_queue_send.push(message);
_var_send.notify_one();
}
void Register(std::any message, MsgBusPtr recv)
{
std::unique_lock<std::mutex> lock(_lock_recv);
_queue_recv[message.type().name()].push_back(recv);
}
void Start()
{
_stop = false;
_message_loop = std::thread(std::bind(&MessageService::MessageLoop, this));
//task.join();
}
void Stop()
{
_stop = true;
}
private:
MessageService() {}
void MessageLoop()
{
while (!_stop)
{
std::any message;
if (1)
{
std::unique_lock<std::mutex> lock(_lock_send);
if (0 == _queue_send.size()) _var_send.wait(lock);
//if (0 == _queue_send.size())continue;
message = _queue_send.front();
_queue_send.pop();
}
std::unique_lock<std::mutex> lock(_lock_recv);
auto type = message.type().name();
if (_queue_recv.end() != _queue_recv.find(type))
{
for (auto recv : _queue_recv[type])
{
std::invoke(std::bind(&IMsgBus::Recv,recv,std::placeholders::_1),message);
}
_queue_recv[type].clear();
}
}
}
std::mutex _lock_recv;
std::mutex _lock_send;
std::condition_variable _var_send;
volatile bool _stop = true;
std::queue<std::any> _queue_send;
std::unordered_map<std::string, std::vector<MsgBusPtr>> _queue_recv;
std::thread _message_loop;
static std::unique_ptr<MessageService> _instance;
};
std::unique_ptr<MessageService> MessageService::_instance;
struct TestMessage
{
std::string msg;
int errid;
};
class MsgBus :public IMsgBus
{
public:
MsgBus() {}
void Recv(std::any message)
{
if (typeid(TestMessage).name() == message.type().name())
{
auto msg = std::any_cast<TestMessage>(message);
std::cout << "11111msg:" << msg.msg << "msgid:" << msg.errid << std::endl;
Sleep(2000);
}
}
};
class MsgBus2 :public IMsgBus
{
public:
MsgBus2() {}
void Recv(std::any message)
{
if (typeid(TestMessage).name() == message.type().name())
{
auto msg = std::any_cast<TestMessage>(message);
std::cout << "22222msg:" << msg.msg << "msgid:" << msg.errid << std::endl;
}
}
};
int main()
{
MessageService::Instance().Start();
TestMessage msg = { "222",10 };
std::any message = msg;
MsgBusPtr ptr = std::shared_ptr<IMsgBus>(new MsgBus());
MsgBusPtr ptr2 = std::shared_ptr<IMsgBus>(new MsgBus2());
MessageService::Instance().Register(TestMessage(), ptr);
MessageService::Instance().Register(TestMessage(), ptr2);
MessageService::Instance().PostMessage(message);
std::cin.get();
}