在工作 GUI 示例中使用 Controller 和 QT Worker

Using Controller and QT Worker in a working GUI example

我创建了一个最小的 QT GUI 示例,以根据 QThread 5.12 documentation.

中推荐的方法从工作线程更新小部件

QThread 5.12 documentation 中所述,Worker class(具有可能很长的 void doWork(const QString ¶meter)方法是:

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }

signals:
    void resultReady(const QString &result);
};

对应的Controllerclass为:

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};

与从 QThread 子 classing 不同,文档中显示的方法显示了使用扩展 QObject 而不是扩展 QThread 的控制器和工作程序的推荐方式并重写 QThread::run 方法,但是它没有说明如何在真实示例的上下文中使用这些方法。

我需要使用 QT Worker 线程,它使用计时器更新 GUI 上的小部件。

我还需要能够停止并 restart/relaunch 使用不同参数的线程,但我在如何正确执行此操作方面遇到了一些问题。指示通过 Controller 和 Worker 执行此操作的首选方法,但连接逻辑有点混乱。

我需要帮助的地方是如何将计时器正确地集成到我的工作线程中,以及如何在当前工作线程完成或被中断并重新启动时停止并重新启动替换工作线程。

我的工作代码由以下文件组成。

Controller.h

#pragma once

// SYSTEM INCLUDES
#include <QObject>
#include <QThread>

// APPLICATION INCLUDES
#include "Worker.h"

// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller(/*MainWindow* mainWindow*/) {
        auto worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &) {
        // how do I update the mainWindow from here
    }
signals:
    void operate(int);
};

Worker.h

#pragma once

// SYSTEM INCLUDES
#include <QTimer>
#include <QObject>
#include <QEventLoop>

// APPLICATION INCLUDES
#include "Worker.h"

// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS

class Worker : public QObject
{
    Q_OBJECT

public slots:
    void doWork(int count)  {
        QString result = "finished";
        // Event loop allocated in workerThread
        // (non-main) thread affinity (as moveToThread)
        // this is important as otherwise it would occur
        // on the main thread.
        QEventLoop loop;
        for (auto i=0; i< count; i++) {
            // wait 1000 ms doing nothing...
            QTimer::singleShot(1000, &loop, SLOT(quit()));
            // process any signals emitted above
            loop.exec();

            emit progressUpdate(i);
        }
        emit resultReady(result);
    }
signals:
    void progressUpdate(int secondsLeft);
    void resultReady(const QString &result);
};

MainWindow.h - 我需要在这里添加一个 Controller 成员。我还在此处添加了一个 updateValue 插槽,我希望在其中更新 GUI。不幸的是,我不知道如何让控制器或工作人员connect从线程发出信号来更新这个插槽。

#pragma once

// SYSTEM INCLUDES
#include <memory>
#include <QMainWindow>

// APPLICATION INCLUDES
// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS
namespace Ui {
class MainWindow;
}

class Controller;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();
    void updateValue(int secsLeft);

private:
    Ui::MainWindow *ui;
    std::unique_ptr<Controller> mpController;
};

MainWindow.cpp -

#include <QThread>

#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "Controller.h"

MainWindow::MainWindow(QWidget *parent) 
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , mpController(std::make_unique<Controller>())
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    emit mpController->operate(100);
}

void MainWindow::updateValue(int secsLeft)
{
    ui->secondsLeft->setText(QString::number(secsLeft));
}

最后是 main.cpp

#include "MainWindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

我基本上需要帮助和解释如何使用集成在我的 GUI 中的 QT Thread controller/worker。

我会尽力回答您在问题中提出的所有问题:

  1. I don't know how to get the controller or the worker to connect a signal from the thread to update this slot.

你自己几乎答对了。

您的 Worker 位于控制器的事件循环中:

+--GUI-thread--+ (main event loop)
| MainWindow,  |
| Controller --o-----> +--QThread--+ (own event loop in ::exec())
+--------------+       | Worker    |
                       +-----------+

Controller 和 Worker 之间的通信必须通过信号槽连接进行。在 MainWindow 和 Controller 之间,信号有助于将依赖性降至最低。

您可以将 Controller 想象成一种中继:来自 MainWindow 的命令通过 Controller 转发给 Worker。 Worker 的结果通过 Controller 转发给任何感兴趣的人。

为此,您可以简单地在控制器中定义信号:

class Controller : public QObject
{
    //...
signals:
    void SignalForwardResult(int result);
};

然后代替

    connect(worker, &Worker::resultReady, this, &Controller::handleResults);

使用新信号:

    connect(worker, &Worker::resultReady, this, &Controller::SignalForwardResult);
    // Yes, you can connect a signal to another signal the same way you would connect to a slot.

并在您的 MainWindow 构造函数中:

//...
ui->setupUi(this);
connect(mpController, &Controller::SignalForwardResult, this, &MainWindow::displayResult);

Worker::progressUpdate() -> Controller::SignalForwardProgress() -> MainWindow::updateValue().

同样
  1. how to stop and restart a replacement worker when the current one has either finished or been interrupted and restarted.

要么为每个任务创建一个新工作人员要么使用一个可以对新任务请求做出反应的持久工作人员。

  • 您通过将任务发送给工作人员 ::doWork() 函数来启动任务。
  • 当长时间的工作完成时,任务自行结束。您会通过工作人员的 resultReady 信号收到通知。
  • 只有通过干预才能取消任务
    • 如果您确实有一个 QTimer,您可以使用一个 cancel() 插槽,因为它将在下一次超时之前在线程的事件循环中调用。
    • 如果您有一个长运行 计算,您需要共享一些您从计算方法内部读取并从您的 GUI 线程设置的标记。我通常为此使用共享的 QAtomicInt 指针,但共享的 bool 通常也足够了。

请注意,当一个方法在线程上 运行 时,该线程的事件循环被阻塞并且在该方法完成之前不会收到任何信号。

不要使用 QCoreApplication::processEvents() 除非你真的知道你在做什么。 (希望你不会!)


  1. how to properly integrate the timer in my worker thread

你不应该。

  • 我猜你使用后台线程是因为有太多的工作要做或者你需要阻塞等待很长时间以至于它会阻塞 GUI,对吧? (如果没有,请考虑不使用线程,为您省去很多麻烦。)

  • 如果你需要一个计时器,让它成为Worker的成员,并将它的parentObject设置为Worker实例。这样,两者将始终具有相同的线程关联。然后,将它连接到一个插槽,如 Worker::timeoutSlot()。在那里你可以发出你的完成信号。