You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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调用:
    1. 起始状态下调用,event->type() == QEvent::None
    2. 起始状态下调用,event->type() == QEvent::StateMachineSignal
    3. 目标状态下调用,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类型被调用,说明这不是单纯的探测行为:

  1. 探测应使用实际事件而非虚拟的QEvent::None
  2. 若探测结果正确,不应出现重复调用
  3. 点击操作应作用于点击瞬间的活跃状态,而非切换后的状态,现有逻辑不符合预期

问题解答

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 04:05:25