Qt状态机疑问:transition的eventTest调用次数超出预期
Qt状态机Transition的eventTest调用异常分析
问题场景
我实现了一个Qt状态机简化示例:通过按钮点击在两个状态s1/s2间切换,用复选框显示当前激活状态,但发现自定义QSignalTransition的eventTest方法调用次数远超预期。
核心代码
#include <QtStateMachine/QStateMachine> #include <QtStateMachine/QSignalTransition> #include <QtWidgets/QApplication> #include <QtWidgets/QCheckBox> #include <QtWidgets/QPushButton> class MySignalTransition : public QSignalTransition { public: MySignalTransition(QString name, QPushButton* button, QCheckBox* checkBox) : QSignalTransition(button, &QAbstractButton::clicked), cb(checkBox) { setObjectName(name); } protected: bool eventTest(QEvent* e) override { qDebug() << objectName() << "tested on" << e->type() << '|' << cb->isChecked(); return QSignalTransition::eventTest(e); } private: QCheckBox* cb; }; int main(int argc, char* argv[]) { QApplication app(argc, argv); app.setQuitOnLastWindowClosed(true); QWidget w; w.setFixedSize(200, 200); QPushButton button(QStringLiteral("Click me"), &w); QCheckBox checkbox(&w); button.move(10, 80); checkbox.move(10, 20); QStateMachine m; QState s1, s2; m.addState(&s1); m.addState(&s2); m.setInitialState(&s1); s1.assignProperty(&checkbox, "checked", false); s2.assignProperty(&checkbox, "checked", true); // 按钮用于在s1和s2间来回切换 MySignalTransition t1("Transition 1", &button, &checkbox), t2("Transition 2", &button, &checkbox); t1.setTargetState(&s2); t2.setTargetState(&s1); s1.addTransition(&t1); s2.addTransition(&t2); w.show(); m.start(); return app.exec(); }
观察到的现象
- 状态机启动时,未点击按钮就会首次调用
eventTest,推测是初始化阶段的正常检查。 - 点击按钮后,会触发三次
eventTest调用:- 起始状态下调用,
event->type() == QEvent::None - 起始状态下调用,
event->type() == QEvent::StateMachineSignal - 目标状态下调用,
event->type() == QEvent::None
- 起始状态下调用,
核心疑问
- 点击按钮时,状态机为什么会以
QEvent::None类型事件再次尝试执行已试过的transition? - 若在
s1/s2中添加其他transition,是否会出现优先级高于信号transition且无法控制的情况?
验证实验
为排查问题,我修改了transition类并添加信号连接:
class MySignalTransition : public QSignalTransition { public: MySignalTransition(QString name, QPushButton* button, QCheckBox* checkBox) : QSignalTransition(button, &QAbstractButton::clicked), cb(checkBox) { setObjectName(name); } protected: bool event(QEvent* e) override { return false; } bool eventTest(QEvent* e) override { qDebug() << objectName() << "tested on" << e->type() << '|' << cb->isChecked(); return transition; } private: QCheckBox* cb; public: inline static bool transition = false; };
QObject::connect(&button, &QPushButton::clicked, []() { MySignalTransition::transition = true; });
点击按钮后,状态机进入无限循环,且断点显示eventTest从未以QEvent::StateMachineSignal类型被调用,说明这不是单纯的探测行为:
- 探测应使用实际事件而非虚拟的
QEvent::None - 若探测结果正确,不应出现重复调用
- 点击操作应作用于点击瞬间的活跃状态,而非切换后的状态,现有逻辑不符合预期
问题解答
1. QEvent::None调用的原因
Qt状态机存在状态稳定性检查机制:
- 当状态机处理任何事件(比如按钮点击的信号事件)前后,都会遍历当前活跃状态的所有transition,用
QEvent::None调用eventTest,目的是检查是否存在无需外部事件触发的自动transition(比如仅依赖条件判断的QAbstractTransition)。 - 点击按钮后的第一次
QEvent::None调用,是状态机在处理信号事件前的预检查;第三次调用则是切换到目标状态后,状态机对新状态执行的稳定性检查。这些都是状态机的内置逻辑,属于正常行为。
2. 无限循环的根源
你重写了QSignalTransition的event方法并返回false,破坏了状态机的事件处理流程:
QSignalTransition::event负责处理信号包装后的QEvent::StateMachineSignal事件,返回false会让状态机认为该transition未处理事件,从而不断重试触发逻辑。- 同时,静态变量
transition的控制逻辑与状态机的事件处理时序不匹配,导致状态机始终无法正确处理信号事件,陷入无限的稳定性检查循环。
3. Transition优先级问题
Qt状态机的transition优先级遵循以下规则:
- 优先处理匹配当前事件类型的transition(比如信号对应的
QEvent::StateMachineSignal事件),再处理无事件的条件transition。 - 同一类型的transition按添加顺序依次检查,第一个满足
eventTest条件的transition会被触发。
只要你遵循状态机的正常逻辑编写transition,不会出现无法控制的优先级问题——异常情况通常源于自定义transition破坏了内置事件处理流程。
解决方案
- 不要随意重写
QSignalTransition的event方法,仅在需要自定义触发条件时重写eventTest,且必须调用父类的QSignalTransition::eventTest(e)来处理信号事件的基础检查。 - 状态机启动时的
eventTest调用是初始化阶段的正常行为,无需额外处理。 - 若要避免状态切换后的稳定性检查,可以将状态设置为最终状态(
QFinalState),但仅适用于不需要后续自动切换的场景。
内容的提问来源于stack exchange,提问作者Atmo
相关产品推荐
相关产品推荐

