0

簡単な状況説明

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とを使用してステート マシンを処理します。TransitionWorker.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、信号を に送信する中間スロットを に配置しようとしましたが、スロットは呼び出されないままです。)WorkerstopProcess()

何かご意見は ?

4

1 に答える 1

2

Oktalist が指摘したように、問題はワーカー スレッドで Qt のイベント ループに戻らないことです。デフォルトでは、Qt は を使用しますQt::AutoConnection。これはQt::DirectConnection、レシーバーが同じスレッドに存在する場合です。そのため、Qt はprocessEvent無限に再帰的に呼び出します。

解決策 1:stopRequested両方のスレッドから書き込み/読み取りを行う。

あなたが示唆したようstopProcessに、Controller直接呼び出すと問題が解決する可能性がありますが、スレッドセーフではありません。stopRequestedとして定義できますが、これはウィンドウvolatileのみ機能し、おそらく他の状況でも機能します。

より良いアプローチは、C++11 がオプションであるかのように定義することです。std::atomic

解決策 2:再帰的な関数呼び出しを避ける

QObject::connect必要な接続の種類を5 番目の引数として指定できます。を選択するQt::QueuedConnectionと、再帰アクションが中断されます。このようにして、Qt はシグナルを処理できるようになりますstopRequested

この方法の利点は、すべてのスレッド セーフの問題が Qt によって透過的に処理されることですが、これによりステート マシンが多少遅くなります。

于 2017-06-01T14:15:48.730 に答える