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

时间:2019-01-13 20:34:59

标签: c++ multithreading qt worker qtwidgets

我创建了一个最小的QT GUI示例,根据QThread 5.12 documentation中的推荐方法,通过工作线程更新小部件。

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

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);
};

,相应的Controller类为:

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的子类,文档中显示的方法显示了推荐的方法,该方法使用控制器和扩展QObject的工作程序,而不是扩展QThread并覆盖{{1} }方法,但是它并未显示在实际示例的上下文中应如何使用它们。

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

我还需要能够使用不同的参数暂停和重新启动/重新启动该线程,并且在正确执行此操作时遇到了一些麻烦。表示通过控制器和工作器执行此操作的首选方式,但连接逻辑有些混乱。

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

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

Controller.h

QThread::run

Worker.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);
};

MainWindow.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); }; 成员。我还在此处希望更新GUI的位置添加了updateValue插槽。不幸的是,我不知道如何让控制器或工作程序从线程中Controller发出信号来更新此插槽。

connect

MainWindow.cpp-

#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;
};

最后是main.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));
}

我基本上需要帮助,并说明如何使用集成在GUI中的QT Thread的控制器/工人。

1 个答案:

答案 0 :(得分:2)

我将尝试回答您要解决的所有问题:

  1.   

    我不知道如何让控制器或工人从线程连接信号来更新此插槽。

您几乎可以做到这一点。

您的工作人员生活在您的控制器的事件循环中:

+--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转发给感兴趣的任何人。

为此,您可以简单地在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.   

    当当前工人已经完成或被中断并重新启动时,如何停止并重新启动更换工人。

要么为每个任务创建一个新工作器,要么使用可以响应新任务请求的持久性工作器。

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

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

请勿使用QCoreApplication::processEvents(),除非您真的知道自己在做什么。 (并希望您不会!)


  1.   

    如何在我的工作线程中正确集成计时器

你不应该。

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

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