当前位置: 首页 > news >正文

工业软件架构1:(QT和C++实现)

工业软件架构 - 事件驱动 - 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 是最佳选择。而线程池更适合需要并行执行和长时间运行的任务。


http://www.mrgr.cn/news/14781.html

相关文章:

  • 基于SOA-BP海鸥优化BP神经网络实现数据预测Python实现
  • 使用arthas调用带参数的方法-傻瓜式教程
  • 网络通信特刊合集(二)——Springer特刊推荐
  • ES配合高德地图JS-API实现地理位置查询
  • Gradle下载失败或者慢怎么办
  • 作业0829
  • 如何利用 Go 语言开发高可用服务
  • 第一个go程序
  • 【LeetCode Cookbook(C++ 描述)】平衡二叉树
  • 使用 `free -m` 命令查看 Linux 系统内存状态
  • PWE3简介
  • 基于SSM+小程序的乡村游小程序登录管理系统(旅游3)(源码+sql脚本+视频导入教程+文档)
  • Cmake教程之一(入门Cmake基础命令)
  • pnpm 查看库的所有版本
  • 005-CircuitBreaker断路器-Resilience4J
  • Mozilla为本地音频到文本翻译开发Whisperfile引擎
  • 力扣1442.形成两个异或相等数组的三元组数目
  • Web之tomcat
  • 王立铭脑科学50讲,50、现在和未来,脑机接口能否带来脑的升级
  • node.js使用express框架实现api接口开发(从零开始,超简单可直接复制)