工业软件架构 - 事件驱动 - 1
- 0.事件总线(EventBus)
- 1. 传感器模块(Sensor Module)
- 2. 硬件控制模块(Hardware Control Module)
- 3. 按键处理模块(Button Handler Module)
- 4. 界面管理模块(UI Module)
- 5. 参数管理模块(Parameter Manager Module)
- 6. 主程序集成与初始化
- 7. 总结
- 8.在设计监控和轮询机制时,选择使用定时器(QTimer)而不是线程池
- 1. Qt 事件驱动架构的优势
- 2. 实时性和响应性
- 3. 线程池的适用场景
- 4. 定时器 vs. 线程池的性能和复杂度对比
- 5. 实际应用中的折中
- 6. 具体应用场景分析
- 传感器监控
- 按键状态监控
- 界面更新
- 总结
0.事件总线(EventBus)
class EventBus : public QObject
{Q_OBJECTpublic:static EventBus* instance() {static EventBus bus;return &bus;}template<typename... Args>void publish(const QString &eventType, Args&&... args){emit eventOccurred(eventType, QVariant::fromValue(std::forward<Args>(args)...));}signals:void eventOccurred(const QString &eventType, const QVariant &data);private:EventBus() = default;~EventBus() = default;
};
1. 传感器模块(Sensor Module)
- 功能: 采集传感器数据,并通过事件总线向其他模块发布传感器数据更新事件。
- 实现: 使用一个管理类来管理所有传感器,并将采集的数据通过事件总线发布。
class SensorModule : public QObject
{Q_OBJECTpublic:SensorModule(QObject *parent = nullptr) : QObject(parent){startMonitoring();}signals:void sensorDataUpdated(int sensorId, double value);private slots:void startMonitoring() {// 模拟传感器数据采集QTimer *timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &SensorModule::collectData);timer->start(100); // 100ms 更新一次}void collectData(){for (int sensorId = 0; sensorId < 100; ++sensorId) {double value = qrand() % 100 / 10.0; // 模拟传感器数据emit sensorDataUpdated(sensorId, value);EventBus::instance()->publish("SensorDataUpdated", sensorId, value);}}
};
2. 硬件控制模块(Hardware Control Module)
- 功能: 监控硬件状态,处理硬件控制命令,并与界面进行同步。
- 实现: 每个硬件设备由一个独立的类进行管理,通过事件总线接收控制命令。
class HardwareControlModule : public QObject{Q_OBJECTpublic:HardwareControlModule(QObject *parent = nullptr) : QObject(parent){connect(EventBus::instance(), &EventBus::eventOccurred, this, &HardwareControlModule::onEvent);}private slots:void onEvent(const QString &eventType, const QVariant &data){if (eventType.startsWith("ControlHardware")){int hardwareId = eventType.section('_', 1, 1).toInt();handleHardwareControl(hardwareId, data);}}void handleHardwareControl(int hardwareId, const QVariant &data){// 处理硬件控制逻辑qDebug() << "Controlling hardware" << hardwareId << "with data" << data;// 控制硬件逻辑EventBus::instance()->publish("HardwareStateUpdated", hardwareId, data);}
};
3. 按键处理模块(Button Handler Module)
- 功能: 实时监控硬件按键状态,并触发相应的操作。
- 实现: 通过中断或轮询方式检测按键状态,触发相应的硬件控制和界面切换。
class ButtonHandlerModule : public QObject {Q_OBJECTpublic:ButtonHandlerModule(QObject *parent = nullptr) : QObject(parent){startMonitoring();}private slots:void startMonitoring(){QTimer *timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &ButtonHandlerModule::checkButtonStates);timer->start(50); // 50ms 检查一次按键状态}void checkButtonStates(){// 模拟按键状态检查for (int buttonId = 0; buttonId < 10; ++buttonId){bool pressed = qrand() % 2; // 模拟按键按下状态if (pressed){handleButtonPress(buttonId);}}}void handleButtonPress(int buttonId){// 根据按键ID发布不同的硬件控制命令或界面切换事件EventBus::instance()->publish(QString("ControlHardware_%1").arg(buttonId), QVariant::fromValue(buttonId));EventBus::instance()->publish(QString("NavigateToPage_%1").arg(buttonId), QVariant::fromValue(buttonId));}
};
4. 界面管理模块(UI Module)
- 功能: 管理 10 个左右的页面,并根据按键操作或硬件状态进行切换和更新。
- 实现: 使用 QStackedWidget 或 QStackedLayout 管理多个页面,并通过事件总线进行页面切换。
class UIManager : public QObject
{Q_OBJECTpublic:UIManager(QStackedWidget *stack, QObject *parent = nullptr) : QObject(parent), stackedWidget(stack) {connect(EventBus::instance(), &EventBus::eventOccurred, this, &UIManager::onEvent);}void registerPage(int pageId, QWidget *page){if (!pageRegistry.contains(pageId)) {pageRegistry[pageId] = page;stackedWidget->addWidget(page);}}private slots:void onEvent(const QString &eventType, const QVariant &data) {if (eventType.startsWith("NavigateToPage")){int pageId = eventType.section('_', 1, 1).toInt();stackedWidget->setCurrentWidget(pageRegistry[pageId]);} else if (eventType == "HardwareStateUpdated"){// 根据硬件状态更新当前页面}}private:QStackedWidget *stackedWidget;QHash<int, QWidget*> pageRegistry;
};
5. 参数管理模块(Parameter Manager Module)
- 功能: 从文件加载初始化参数,修改后保存,并与硬件和界面同步。
- 实现: 使用一个管理类来加载和保存参数,并通过事件总线发布参数更新事件。
class ParameterManager : public QObject {Q_OBJECTpublic:ParameterManager(QObject *parent = nullptr) : QObject(parent){loadParameters();}void loadParameters(){// 从文件加载参数QFile file("parameters.json");if (file.open(QIODevice::ReadOnly)) {QByteArray data = file.readAll();QJsonDocument doc = QJsonDocument::fromJson(data);parameters = doc.object().toVariantMap();file.close();// 发布加载完成的参数EventBus::instance()->publish("ParametersLoaded", parameters);}}void saveParameters(){QFile file("parameters.json");if (file.open(QIODevice::WriteOnly)) {QJsonDocument doc(QJsonObject::fromVariantMap(parameters));file.write(doc.toJson());file.close();}}QVariant getParameter(const QString &key) const {return parameters.value(key);}void setParameter(const QString &key, const QVariant &value) {parameters[key] = value;EventBus::instance()->publish("ParameterUpdated", key, value);saveParameters();}private:QVariantMap parameters;
};
6. 主程序集成与初始化
主程序将初始化所有模块,并通过事件总线连接各个模块,实现实时监控和响应。
int main(int argc, char *argv[])
{QApplication app(argc, argv);// 初始化堆叠界面QStackedWidget stackedWidget;// 初始化模块SensorModule sensorModule;HardwareControlModule hardwareControlModule;ButtonHandlerModule buttonHandlerModule;ParameterManager parameterManager;UIManager uiManager(&stackedWidget);// 注册页面for (int i = 0; i < 10; ++i){QWidget *page = new QWidget();QLabel *label = new QLabel(QString("Page %1").arg(i + 1), page);QVBoxLayout *layout = new QVBoxLayout(page);layout->addWidget(label);uiManager.registerPage(i, page);}// 显示堆叠窗口stackedWidget.show();return app.exec();
}
7. 总结
这个架构充分考虑了实时性、模块化、和可扩展性,适应了复杂硬件环境下的多功能需求。关键点如下:
- 模块化设计:各个功能通过模块化设计进行管理,减少耦合度,增加系统的灵活性。
- 事件驱动:事件总线在各模块间传递事件,实现模块间的松耦合和实时通信。
- 实时响应:传感器数据、硬件状态和按键操作均通过事件总线实时更新,确保系统的及时响应。
- 界面管理:使用 QStackedWidget 管理多个页面,并通过事件总线实现页面的动态切换和更新。
- 参数管理:文件系统与应用程序参数保持同步,确保参数的一致性和持久性。
- 通过这种架构,软件可以在复杂硬件环境中高效、可靠地运行,并提供实时的监控和控制功能。
8.在设计监控和轮询机制时,选择使用定时器(QTimer)而不是线程池
1. Qt 事件驱动架构的优势
Qt 框架本身是基于事件驱动的,这意味着它特别适合处理异步事件和响应用户交互。定时器是 Qt 提供的一种简单且高效的机制,可以在主线程的事件循环中定期执行任务,而不必创建额外的线程。
- 轻量级: QTimer 在主线程中运行,不需要创建和管理多个线程,因此它的资源消耗非常低,特别是对于短时间间隔的任务,它非常高效。
- 线程安全: 由于 QTimer 在主线程的事件循环中运行,所以你不必担心线程同步的问题。所有的事件处理都在同一个线程中进行,避免了跨线程访问共享资源时的复杂性。
2. 实时性和响应性
在需要实时监控传感器数据、按键状态等的场景下,使用 QTimer 进行定时轮询可以确保操作在主线程中快速执行,并立即响应结果。
- 快速响应: 因为操作在主线程中执行,所以响应时间非常短。事件循环能够确保每次定时器触发时立即处理任务,而不用等待线程上下文切换。
- 适合频繁的短时任务: 对于频繁且短时间的任务,如每 50ms 检查按键状态,QTimer 可以提供非常稳定和精确的定时机制,而不需要启动或销毁线程。
3. 线程池的适用场景
线程池(QThreadPool)和 QRunnable 更适合用于处理较长时间运行的任务或需要并行执行的任务。
- 并行计算: 线程池适用于计算密集型任务或者需要并行执行的任务,而非简单的轮询或状态检查。
- 避免主线程阻塞: 当任务可能会阻塞主线程时,使用线程池可以将这些任务移到后台线程中执行,而不影响主线程的界面响应。
监控传感器数据和按键状态的操作都是非常频繁的、耗时非常短的任务,使用 QTimer 在主线程中处理这些任务更为合适。
4. 定时器 vs. 线程池的性能和复杂度对比
- 资源消耗: 线程池会创建多个线程,并需要进行线程管理(如上下文切换、线程同步),这对资源的消耗较大,且管理复杂。QTimer 则没有这种开销。
- 开发复杂度: 使用线程池需要考虑线程间同步和资源共享的复杂性。相较之下,QTimer 提供了更简洁的实现方式,避免了多线程编程的复杂性。
5. 实际应用中的折中
当然,如果你的监控任务变得非常复杂,或者涉及到长时间运行的操作,那么可以考虑结合使用 QTimer 和线程池:
- 定时器触发线程池任务: 使用 QTimer 定期触发任务,然后将较重的任务委托给线程池处理,主线程继续保持响应性。
- 多线程监控: 如果传感器数据的采集或按键监控变得非常复杂,且需要并行处理,可以将不同的任务分配给不同的线程池实例。
6. 具体应用场景分析
传感器监控
- 使用 QTimer: 传感器状态的监控通常是频繁且快速的操作。例如,每隔 100ms 轮询一次传感器状态。在这种情况下,使用 QTimer 在主线程中定期轮询传感器是一个合理的选择,因为轮询操作通常是轻量级的。
void SensorModule::startMonitoring(){QTimer *timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &SensorModule::collectData);timer->start(100); // 100ms 更新一次 }
- 使用线程池: 如果传感器的数据处理需要耗时的计算,或者多个传感器的数据需要并行处理,那么可以使用线程池来处理这些任务,将数据采集和处理分离开来。
void SensorModule::collectData() {for (int sensorId = 0; sensorId < 100; ++sensorId){QtConcurrent::run([this, sensorId](){double value = getSensorData(sensorId); // 假设这是一个耗时操作emit sensorDataUpdated(sensorId, value);});} }
按键状态监控
-
使用 QTimer: 按键状态的监控通常也是频繁且快速的操作(例如每 50ms 轮询一次按键状态)。按键检测通常不会耗费大量时间,因此使用 QTimer 进行轮询是合理的。
void ButtonHandlerModule::startMonitoring() {QTimer *timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &ButtonHandlerModule::checkButtonStates);timer->start(50); // 50ms 检查一次按键状态 }
-
使用线程池: 如果按键按下后会触发一些耗时操作,比如复杂的硬件控制或计算任务,这些操作应该委托给线程池处理,而不是直接在 QTimer 的槽函数中执行,以避免阻塞主线程。
void ButtonHandlerModule::handleButtonPress(int buttonId) {Command *command = createCommandForButton(buttonId);if (command) {QtConcurrent::run([command]() {command->execute(); // 在后台线程中执行命令delete command;});} }
界面更新
- 使用 QTimer: 如果有需要定期刷新界面的操作,比如显示传感器数据或硬件状态,那么 QTimer 是一个合适的工具。
void UIManager::startAutoRefresh() {QTimer *timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &UIManager::refreshDisplay);timer->start(1000); // 每秒刷新一次 }
- 使用线程池: 当界面更新依赖于复杂的后台计算结果时,可以将计算部分放到线程池中,计算完成后通过 QMetaObject::invokeMethod 或 QTimer::singleShot 在主线程中更新界面。
void SomeViewModel::performComplexCalculation(){QtConcurrent::run([this]() {QVariant result = performCalculation();QMetaObject::invokeMethod(this, [this, result]() {this->updateUI(result); // 确保在主线程中更新 UI});}); }
总结
在这种实时监控和响应的场景下,QTimer 是一种更轻量级和合适的选择,它充分利用了 Qt 的事件驱动机制,简化了代码的实现,并保证了系统的响应性。如果监控任务较简单且需要频繁执行,使用 QTimer 是最佳选择。而线程池更适合需要并行执行和长时间运行的任务。