为什么我的 Qt 信号没有被事件队列处理?
Why my Qt signal is not processed by the event queue?
情况的简要描述
我正在尝试让一个最小的 GUI 开始一个无尽的过程,该过程通过 CAN 总线通过自定义协议进行通信。
根据我所阅读的内容 here,我的代码结构如下:
我一方面有一个 class 使用 2 个简单的按钮 "start" 和 "stop" 处理我的 GUI,即 MainWindow。
另一方面,class 使用上面 link 中描述的状态机管理我的自定义协议,即 Worker。
在这些中间,我有一个控制器 link 将所有内容放在一起。这个控制器在这里是因为处理了一些其他任务,但这不是这个 post.
的目的
关于信号和槽
我已将我的按钮信号 (released()) 连接到来自控制器的信号。所以 GUI 不知道到底是什么开始了。
那些 Controller 的信号连接到 Worker 的插槽。这些插槽用于启动和停止进程。
关于线程
Worker 实例存在于它自己的 QThread 中。可能涉及其他任务,所以我发现最好在自己的线程中处理每个任务。
一开始,worker 的进程是通过 signals/slots 处理的,这使得状态机在有关转换的状态之间进化。由于 signal/slot 机制,线程的事件循环可以处理来自其队列的事件,如果我是正确的话。
问题
我的启动信号已正确发送给工作人员,从而启动了进程和状态机。这台机器是循环的,直到用户请求停止信号。但是,当用户单击按钮 "stop" 时,不会调用关联的插槽。同时,机器继续无休止地运行并且没有看到停止请求(我已经放了一些调试消息以查看真正执行的内容)。
代码片段
这里是代码片段。
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "controller.h"
class QPushButton;
class QWidget;
class QVBoxLayout;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(Controller& controller, QWidget *parent = 0);
~MainWindow();
private:
Controller& controller;
QPushButton* startButton;
QPushButton* stopButton;
QWidget* centralWidget;
QVBoxLayout* layout;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include <QWidget>
#include <QVBoxLayout>
#include <QPushButton>
MainWindow::MainWindow(Controller &controller, QWidget *parent) :
QMainWindow(parent), controller(controller)
{
centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
layout = new QVBoxLayout();
startButton = new QPushButton("START", this);
stopButton = new QPushButton("STOP", this);
layout->addWidget(startButton);
layout->addWidget(stopButton);
centralWidget->setLayout(layout);
connect(startButton, SIGNAL(released()), &controller, SIGNAL(startSignal()));
connect(stopButton, SIGNAL(released()), &controller, SIGNAL(stopSignal()));
}
MainWindow::~MainWindow()
{
delete stopButton;
delete startButton;
delete layout;
delete centralWidget;
}
Controller.h
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include <QThread>
class MainWindow;
class Worker;
class Controller : public QObject
{
Q_OBJECT
public:
Controller();
virtual ~Controller();
signals:
void startSignal() const;
void stopSignal() const;
private:
MainWindow* mainWindow;
QThread workerThread;
Worker* worker;
};
#endif // CONTROLLER_H
Controller.cpp(继承public QObject)
#include "controller.h"
#include "mainwindow.h"
#include "worker.h"
Controller::Controller()
{
mainWindow = new MainWindow(*this);
mainWindow->show();
worker = new Worker();
worker->moveToThread(&workerThread);
connect(this, SIGNAL(startSignal()), worker, SLOT(startProcess()));
connect(this, SIGNAL(stopSignal()), worker, SLOT(stopProcess()));
workerThread.start();
}
Controller::~Controller()
{
workerThread.quit();
workerThread.wait();
delete worker;
delete mainWindow;
}
Worker 使用 State
和 Transition
枚举来处理状态机。
Worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
enum State { IDLE, STATE_1, STATE_2 };
enum Transition { OK, ERROR };
enum Mode { MODE_1, MODE_2 };
explicit Worker();
void read();
public slots:
void startProcess();
void stopProcess();
void processEvent(const Transition& transition);
signals:
void sendSignal(const Transition& transition) const;
private:
State currentState;
Mode selectedMode;
bool stopRequested;
};
#endif // WORKER_H
Worker.cpp(继承public QObject)
#include "worker.h"
#include <QDebug>
#include <QThread>
Worker::Worker() : QObject()
{
stopRequested = false;
currentState = IDLE;
connect(this, SIGNAL(sendSignal(Transition)), this, SLOT(processEvent(Transition)));
}
void Worker::read()
{
qDebug() << "Reading...";
QThread::msleep(500);
emit sendSignal(OK);
}
void Worker::startProcess()
{
qDebug() << "Start requested";
selectedMode = MODE_1;
stopRequested = false;
emit sendSignal(OK);
}
void Worker::stopProcess()
{
qDebug() << "Stop requested";
stopRequested = true;
}
void Worker::processEvent(const Worker::Transition &transition)
{
qDebug() << "Process event";
switch(currentState) {
case IDLE:
switch(selectedMode) {
case MODE_1:
currentState = STATE_1;
read();
break;
case MODE_2:
currentState = STATE_2;
break;
}
break;
case STATE_1:
if (!stopRequested) {
if (transition == OK) {
read();
} else {
currentState = IDLE;
// No emission. The state machine stops on error
}
}
break;
case STATE_2:
// Not implemented yet
break;
}
}
.pro 文件
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = sample_project
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += main.cpp\
mainwindow.cpp \
controller.cpp \
worker.cpp
HEADERS += mainwindow.h \
controller.h \
worker.h
免责声明 代码未正确退出。最好在你的 IDE 中启动它,这样你就可以轻松地杀死它。
这些代码片段是使用 Qt5.8.0 MinGW 32 位构建的。
要重现该问题,只需点击 "start",调试消息就会出现在控制台中。然后点击 "stop",消息不断出现,并且没有停止。
我找到了一个解决方法,通过从 Controller
直接调用 stopProcess()
而不是使用信号。这样做会正确设置 stopRequested
并停止进程。
不过,我想知道为什么事件队列从不处理来自 Controller
的信号?即使使用 signal/slots 处理状态机,也允许事件队列在事件到达时对其进行处理。
(我尝试在 Controller
中放置一个中间插槽,将信号发送到 Worker
以查看 GUI 是否正确发送了信号并且确实执行了此插槽。但是 stopProcess()
插槽仍未调用。)
有什么想法吗?
正如 Oktalist 所指出的,问题是您永远不会在工作线程中返回到 Qt 的事件循环。默认情况下,Qt 使用 Qt::AutoConnection
,如果接收器位于同一个线程中,则为 Qt::DirectConnection
。因此,Qt 以无限的方式递归调用 processEvent
。
解决方案 1: write/read stopRequested
来自两个线程。
如您所建议,直接从 Controller
调用 stopProcess
可能会解决您的问题,但不是线程安全的。您可以将 stopRequested
定义为 volatile
,但这只会 work on windows and will probably work in other situations.
如果 C++11 适合您,更好的方法是 define it as std::atomic
。
方案二:避免递归函数调用
您可以在 QObject::connect
的第五个参数中指定您想要的连接类型。选择 Qt::QueuedConnection
将中断您的递归操作。这样,Qt 就能处理你的 stopRequested
信号。
这种方法的优点是所有线程安全问题都由 Qt 透明地处理,但这会使您的状态机稍微慢一些。
情况的简要描述
我正在尝试让一个最小的 GUI 开始一个无尽的过程,该过程通过 CAN 总线通过自定义协议进行通信。
根据我所阅读的内容 here,我的代码结构如下:
我一方面有一个 class 使用 2 个简单的按钮 "start" 和 "stop" 处理我的 GUI,即 MainWindow。
另一方面,class 使用上面 link 中描述的状态机管理我的自定义协议,即 Worker。
在这些中间,我有一个控制器 link 将所有内容放在一起。这个控制器在这里是因为处理了一些其他任务,但这不是这个 post.
的目的关于信号和槽
我已将我的按钮信号 (released()) 连接到来自控制器的信号。所以 GUI 不知道到底是什么开始了。
那些 Controller 的信号连接到 Worker 的插槽。这些插槽用于启动和停止进程。
关于线程
Worker 实例存在于它自己的 QThread 中。可能涉及其他任务,所以我发现最好在自己的线程中处理每个任务。
一开始,worker 的进程是通过 signals/slots 处理的,这使得状态机在有关转换的状态之间进化。由于 signal/slot 机制,线程的事件循环可以处理来自其队列的事件,如果我是正确的话。
问题
我的启动信号已正确发送给工作人员,从而启动了进程和状态机。这台机器是循环的,直到用户请求停止信号。但是,当用户单击按钮 "stop" 时,不会调用关联的插槽。同时,机器继续无休止地运行并且没有看到停止请求(我已经放了一些调试消息以查看真正执行的内容)。
代码片段
这里是代码片段。 MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "controller.h"
class QPushButton;
class QWidget;
class QVBoxLayout;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(Controller& controller, QWidget *parent = 0);
~MainWindow();
private:
Controller& controller;
QPushButton* startButton;
QPushButton* stopButton;
QWidget* centralWidget;
QVBoxLayout* layout;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include <QWidget>
#include <QVBoxLayout>
#include <QPushButton>
MainWindow::MainWindow(Controller &controller, QWidget *parent) :
QMainWindow(parent), controller(controller)
{
centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
layout = new QVBoxLayout();
startButton = new QPushButton("START", this);
stopButton = new QPushButton("STOP", this);
layout->addWidget(startButton);
layout->addWidget(stopButton);
centralWidget->setLayout(layout);
connect(startButton, SIGNAL(released()), &controller, SIGNAL(startSignal()));
connect(stopButton, SIGNAL(released()), &controller, SIGNAL(stopSignal()));
}
MainWindow::~MainWindow()
{
delete stopButton;
delete startButton;
delete layout;
delete centralWidget;
}
Controller.h
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include <QThread>
class MainWindow;
class Worker;
class Controller : public QObject
{
Q_OBJECT
public:
Controller();
virtual ~Controller();
signals:
void startSignal() const;
void stopSignal() const;
private:
MainWindow* mainWindow;
QThread workerThread;
Worker* worker;
};
#endif // CONTROLLER_H
Controller.cpp(继承public QObject)
#include "controller.h"
#include "mainwindow.h"
#include "worker.h"
Controller::Controller()
{
mainWindow = new MainWindow(*this);
mainWindow->show();
worker = new Worker();
worker->moveToThread(&workerThread);
connect(this, SIGNAL(startSignal()), worker, SLOT(startProcess()));
connect(this, SIGNAL(stopSignal()), worker, SLOT(stopProcess()));
workerThread.start();
}
Controller::~Controller()
{
workerThread.quit();
workerThread.wait();
delete worker;
delete mainWindow;
}
Worker 使用 State
和 Transition
枚举来处理状态机。
Worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
enum State { IDLE, STATE_1, STATE_2 };
enum Transition { OK, ERROR };
enum Mode { MODE_1, MODE_2 };
explicit Worker();
void read();
public slots:
void startProcess();
void stopProcess();
void processEvent(const Transition& transition);
signals:
void sendSignal(const Transition& transition) const;
private:
State currentState;
Mode selectedMode;
bool stopRequested;
};
#endif // WORKER_H
Worker.cpp(继承public QObject)
#include "worker.h"
#include <QDebug>
#include <QThread>
Worker::Worker() : QObject()
{
stopRequested = false;
currentState = IDLE;
connect(this, SIGNAL(sendSignal(Transition)), this, SLOT(processEvent(Transition)));
}
void Worker::read()
{
qDebug() << "Reading...";
QThread::msleep(500);
emit sendSignal(OK);
}
void Worker::startProcess()
{
qDebug() << "Start requested";
selectedMode = MODE_1;
stopRequested = false;
emit sendSignal(OK);
}
void Worker::stopProcess()
{
qDebug() << "Stop requested";
stopRequested = true;
}
void Worker::processEvent(const Worker::Transition &transition)
{
qDebug() << "Process event";
switch(currentState) {
case IDLE:
switch(selectedMode) {
case MODE_1:
currentState = STATE_1;
read();
break;
case MODE_2:
currentState = STATE_2;
break;
}
break;
case STATE_1:
if (!stopRequested) {
if (transition == OK) {
read();
} else {
currentState = IDLE;
// No emission. The state machine stops on error
}
}
break;
case STATE_2:
// Not implemented yet
break;
}
}
.pro 文件
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = sample_project
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += main.cpp\
mainwindow.cpp \
controller.cpp \
worker.cpp
HEADERS += mainwindow.h \
controller.h \
worker.h
免责声明 代码未正确退出。最好在你的 IDE 中启动它,这样你就可以轻松地杀死它。
这些代码片段是使用 Qt5.8.0 MinGW 32 位构建的。 要重现该问题,只需点击 "start",调试消息就会出现在控制台中。然后点击 "stop",消息不断出现,并且没有停止。
我找到了一个解决方法,通过从 Controller
直接调用 stopProcess()
而不是使用信号。这样做会正确设置 stopRequested
并停止进程。
不过,我想知道为什么事件队列从不处理来自 Controller
的信号?即使使用 signal/slots 处理状态机,也允许事件队列在事件到达时对其进行处理。
(我尝试在 Controller
中放置一个中间插槽,将信号发送到 Worker
以查看 GUI 是否正确发送了信号并且确实执行了此插槽。但是 stopProcess()
插槽仍未调用。)
有什么想法吗?
正如 Oktalist 所指出的,问题是您永远不会在工作线程中返回到 Qt 的事件循环。默认情况下,Qt 使用 Qt::AutoConnection
,如果接收器位于同一个线程中,则为 Qt::DirectConnection
。因此,Qt 以无限的方式递归调用 processEvent
。
解决方案 1: write/read stopRequested
来自两个线程。
如您所建议,直接从 Controller
调用 stopProcess
可能会解决您的问题,但不是线程安全的。您可以将 stopRequested
定义为 volatile
,但这只会 work on windows and will probably work in other situations.
如果 C++11 适合您,更好的方法是 define it as std::atomic
。
方案二:避免递归函数调用
您可以在 QObject::connect
的第五个参数中指定您想要的连接类型。选择 Qt::QueuedConnection
将中断您的递归操作。这样,Qt 就能处理你的 stopRequested
信号。
这种方法的优点是所有线程安全问题都由 Qt 透明地处理,但这会使您的状态机稍微慢一些。