工业级大型 Qt / C++ 项目中 EventBus 的实现方式,我会尽量详细、结合实际应用场景讲,涵盖线程安全、异步、类型安全和模块自动注册。
一、EventBus 的核心目标
在大型系统中,EventBus 主要解决三个问题:
- 模块解耦:模块 A 不直接调用模块 B,只通过事件通信。
- 扩展性强:新增模块只需订阅事件,不改已有模块。
- 线程安全:大型系统多线程执行,EventBus 必须保证事件在不同线程安全传递。
二、工业级 EventBus 的关键特点
| 特性 | 说明 | 举例 |
|---|---|---|
| 类型安全 | 事件类型明确,避免使用 void* 或字符串匹配 |
struct MotorMovedEvent { int motorId; double pos; }; |
| 异步 / 同步 | 支持发布事件后立即处理(同步),或延迟处理(异步) | MotorController 发布事件 → InspectionController 异步接收 |
| 线程安全 | 事件在不同线程之间安全传递 | UI 线程发事件,Worker 线程处理 |
| 模块自动注册 | 模块注册自己的事件和回调,EventBus 自动管理 | 启动时所有 Controller 自动订阅自己关心的事件 |
三、事件类型设计
工业级项目通常用 结构体 + C++ 模板来保证类型安全:
cpp
// 事件定义
struct MotorMovedEvent {
int motorId;
double position;
};
struct ImageCapturedEvent {
int cameraId;
cv::Mat image; // OpenCV 图像
};
这样在编译期就能检查事件类型,而不是靠字符串。
四、EventBus 类设计
工业级项目通常做成 单例 + 模板注册 + 信号槽 +线程安全:
cpp
#include <functional>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <typeindex>
class EventBus {
public:
// 单例
static EventBus& instance() {
static EventBus bus;
return bus;
}
// 订阅事件
template<typename EventType>
void subscribe(std::function<void(const EventType&)> callback) {
std::lock_guard<std::mutex> lock(mutex_);
auto& vec = subscribers_[std::type_index(typeid(EventType))];
vec.push_back([callback](const auto& e) {
callback(*static_cast<const EventType*>(&e));
});
}
// 发布事件
template<typename EventType>
void publish(const EventType& event) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = subscribers_.find(std::type_index(typeid(EventType)));
if(it != subscribers_.end()) {
for(auto& cb : it->second) {
cb(event); // 同步调用
}
}
}
private:
EventBus() = default;
std::unordered_map<std::type_index, std::vector<std::function<void(const void&)>>> subscribers_;
std::mutex mutex_;
};
五、使用示例
1️⃣ 模块 A 发布事件
cpp
MotorMovedEvent e{1, 100.0};
EventBus::instance().publish(e);
2️⃣ 模块 B 订阅事件
cpp
EventBus::instance().subscribe<MotorMovedEvent>(
[](const MotorMovedEvent& e){
qDebug() << "Motor" << e.motorId << "moved to" << e.position;
}
);
✅ 此时模块 A 和 B 完全解耦。
六、异步事件处理(工业级必备)
工业控制软件很多事件需要异步处理,比如:
- 电机移动完成 → 图像采集
- 摄像头拍照完成 → 算法处理
Qt 可以利用 QMetaObject::invokeMethod + QueuedConnection 或 自定义线程队列:
cpp
template<typename EventType>
void publishAsync(const EventType& event) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = subscribers_.find(std::type_index(typeid(EventType)));
if(it != subscribers_.end()) {
for(auto& cb : it->second) {
// 异步调用
QtConcurrent::run([cb, event](){ cb(event); });
}
}
}
这样就不会阻塞发布者线程。
七、模块自动注册(工业级项目常用)
在大型项目中:
- 每个 Controller / Service 启动时,会自己订阅关心的事件
- EventBus 提供注册接口,模块不用关心其他模块
例如:
cpp
class InspectionController {
public:
InspectionController() {
EventBus::instance().subscribe<MotorMovedEvent>(
[this](const MotorMovedEvent& e){ onMotorMoved(e); }
);
}
void onMotorMoved(const MotorMovedEvent& e) {
camera.capture();
}
};
模块一旦创建,订阅就自动生效。
八、线程安全 & 高性能优化
工业级软件对性能和线程安全要求高:
- 互斥锁 (
std::mutex)保证多线程安全 - 读写锁 (
QReadWriteLock)优化高频事件订阅 - 事件队列 + worker线程 处理异步事件
- 事件优先级:重要事件可以加队列优先级
例如:
cpp
struct EventWrapper {
int priority;
std::function<void()> callback;
};
事件总线可以维护一个 优先队列,保证关键事件先处理。
九、真实工业案例应用
例子 1:自动光学检测设备
- 电机完成 → 发布
MotorMovedEvent - CameraController 订阅 → 拍照 → 发布
ImageCapturedEvent - InspectionController 订阅 → 算法分析 → 发布
InspectionResultEvent - NetworkController 订阅 → 上传结果
整个流程:
text
Motor → EventBus → Camera → EventBus → Algorithm → EventBus → Network
没有模块直接互相调用。
例子 2:多传感器温控系统
- 温度传感器 → 发布
TemperatureEvent - CoolingController 订阅 → 调节冷却
- AlarmSystem 订阅 → 超温报警
- Logger 订阅 → 记录数据
模块增加再多,EventBus 不需要改动核心逻辑。
十、总结
工业级 Qt / C++ EventBus 关键点:
- 类型安全:每种事件是 C++ 类型
- 线程安全:多线程系统必备
- 同步 / 异步发布:不同事件需求不同
- 模块自动注册:模块创建即可订阅事件
- 易扩展:新增模块无需修改已有模块
💡 工业项目实践经验:
大型项目没 EventBus,模块耦合会快速失控。
EventBus 是大型 Qt / C++ 工程的"血脉",控制系统、机器人、工业设备几乎都离不开它。
