簡単な状況説明
CAN バスを介してカスタム プロトコルを介して通信するエンドレス プロセスを開始する最小限の GUI を使用しようとしています。
ここで読んだ内容に基づいて、コードを次のように構成しました。
一方で、2 つの単純なプッシュ ボタン「開始」と「停止」、つまり MainWindow を使用して GUI を処理するクラスがあります。
一方、上記のリンクで説明されているように、ステート マシンを使用してカスタム プロトコルを管理するクラス、つまり Worker.
その中間に、全体をつなぐコントローラーがあります。このコントローラーがここにあるのは、他のいくつかのタスクが処理されるためですが、これはこの記事の目的ではありません。
シグナルとスロットについて
ボタン信号 (released()) をコントローラーからの信号に接続しました。そのため、GUI は正確に何が開始されているかを認識していません。
これらのコントローラーの信号は、ワーカーからのスロットに接続されます。これらのスロットは、プロセスを開始および停止するためにあります。
スレッドについて
Worker インスタンスは独自の QThread に存在します。他のタスクが関与している可能性があるため、それぞれを独自のスレッドで処理する方がよいと判断しました。
開始すると、ワーカーのプロセスは、遷移に関する状態全体で状態マシンを進化させるシグナル/スロットを介して処理されます。私が正しければ、シグナル/スロット メカニズムにより、スレッドのイベント ループはそのキューからイベントを処理できます。
問題
私の開始信号はワーカーに正しく送信され、プロセスが開始され、したがってステートマシンが開始されます。このマシンは、停止信号がユーザーによって要求されるまで周期的です。ただし、ユーザーが「停止」ボタンをクリックすると、関連付けられているスロットは呼び出されません。その間、マシンは際限なく実行され続け、停止要求が表示されません (実際に何が実行されたかを確認するために、いくつかのデバッグ メッセージを表示しました)。
コード スニペット
コード スニペットを次に示します。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 (パブリック 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 (パブリック 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 ビットでビルドされています。問題を再現するには、「開始」を押すだけで、コンソールにデバッグ メッセージが表示されます。次に、「停止」を押すと、メッセージが来続け、本来のように停止しません。
シグナルを使用する代わりにstopProcess()
から直接呼び出すことで回避策を見つけました。Controller
そうすることで、正しく設定さstopRequested
れ、プロセスが停止します。
Controller
ただし、イベントキューが?からの信号を処理しないのはなぜだろうと思っていました。ステート マシンがシグナル/スロットで処理されている場合でも、イベント キューはイベントが到着したときにイベントを処理できます。
( GUI が信号を正しく送信し、このスロットが実際に実行されるかどうかを確認するためにController
、信号を に送信する中間スロットを に配置しようとしましたが、スロットは呼び出されないままです。)Worker
stopProcess()
何かご意見は ?