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