0

我的代码由一个工人类和一个对话框类组成。工人阶级开始了一份工作(一份很长的工作)。我的对话框类有 2 个按钮,允许启动和停止作业(它们工作正常)。我想实现一个显示工作正在进行的繁忙栏。我在 Worker 类中使用了 QProgressDialog。当我想使用 Qpr​​ogressDialogcancel按钮停止工作时,我无法捕捉到信号&QProgressDialog::canceled。我试过了,这个(放在 Worker 构造函数中):

QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);

没有任何影响。

您可以在下面看到完整的编译代码。

如何通过单击 QprogressDialog 取消按钮来停止作业?

下面是我在必要时重现该行为的完整代码。

//worker.h

#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QProgressDialog>
class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr);
    virtual ~Worker();
    QProgressDialog * getProgress() const;
    void setProgress(QProgressDialog *value);
signals:
    void sigAnnuler(bool);
    // pour dire que le travail est fini
    void sigFinished();
    // mise à jour du progression bar
    void sigChangeValue(int);
public slots:
    void doWork();
    void stopWork();
private:
    bool workStopped = false;
    QProgressDialog* progress = nullptr;
};
#endif // WORKER_H

// worker.cpp

#include "worker.h"
#include <QtConcurrent>
#include <QThread>
#include <functional>
// Worker.cpp
Worker::Worker(QObject* parent/*=nullptr*/)
{
    //progress = new QProgressDialog("Test", "Test", 0, 0);
    QProgressDialog* progress = new QProgressDialog("do Work", "Annuler", 0, 0);
    progress->setMinimumDuration(0);
    QObject::connect(this, &Worker::sigChangeValue, progress, &QProgressDialog::setValue);
    QObject::connect(this, &Worker::sigFinished, progress, &QProgressDialog::close);
    QObject::connect(this, &Worker::sigAnnuler, progress, &QProgressDialog::cancel);
    QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);
}
Worker::~Worker()
{
    //delete timer;
    delete progress;
}
void Worker::doWork()
{
    emit sigChangeValue(0);

    for (int i=0; i< 100; i++)
    {

       qDebug()<<"work " << i;
       emit sigChangeValue(0);
       QThread::msleep(100);

       if (workStopped)
       {
           qDebug()<< "Cancel work";
           break;
       }
       
    }
    emit sigFinished();
}
void Worker::stopWork()
{
    workStopped = true;
}
QProgressDialog *Worker::getProgress() const
{
    return progress;
}
void Worker::setProgress(QProgressDialog *value)
{
    progress = value;
}

// mydialog.h

#ifndef MYDIALOG_H
#define MYDIALOG_H

#include <QDialog>
#include "worker.h"

namespace Ui {
class MyDialog;
}

class MyDialog : public QDialog
{
    Q_OBJECT

public:
    explicit MyDialog(QWidget *parent = 0);
    ~MyDialog();
    void triggerWork();
    void StopWork();
private:
    Ui::MyDialog *ui;
    QThread* m_ThreadWorker = nullptr;
    Worker* m_TraitementProdCartoWrkr = nullptr;
};

#endif // MYDIALOG_H
#include "mydialog.h"
#include "ui_mydialog.h"
#include <QProgressDialog>
#include <QThread>

MyDialog::MyDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::MyDialog)
{
    ui->setupUi(this);
    m_TraitementProdCartoWrkr = new Worker(this);
    connect(ui->OK, &QPushButton::clicked, this, &MyDialog::triggerWork);
    connect(ui->Cancel, &QPushButton::clicked, this, &MyDialog::StopWork);
}
MyDialog::~MyDialog()
{
    delete ui;
}
void MyDialog::triggerWork()
{
    m_ThreadWorker = new QThread;
    QProgressDialog* progress = m_TraitementProdCartoWrkr->getProgress();
    m_TraitementProdCartoWrkr->moveToThread(m_ThreadWorker);
    QObject::connect(m_ThreadWorker, &QThread::started, m_TraitementProdCartoWrkr, &Worker::doWork);
    m_ThreadWorker->start();
}

void MyDialog::StopWork()
{
    m_TraitementProdCartoWrkr->stopWork();
}

// main.cpp

#include "mydialog.h"
#include "ui_mydialog.h"
#include <QProgressDialog>
#include <QThread>

MyDialog::MyDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::MyDialog)
{
    ui->setupUi(this);
    m_TraitementProdCartoWrkr = new Worker(this);
    connect(ui->OK, &QPushButton::clicked, this, &MyDialog::triggerWork);
    connect(ui->Cancel, &QPushButton::clicked, this, &MyDialog::StopWork);
}

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

void MyDialog::triggerWork()
{
    m_ThreadWorker = new QThread;

    QProgressDialog* progress = m_TraitementProdCartoWrkr->getProgress();

    m_TraitementProdCartoWrkr->moveToThread(m_ThreadWorker);
    QObject::connect(m_ThreadWorker, &QThread::started, m_TraitementProdCartoWrkr, &Worker::doWork);
    //QObject::connect(m_ThreadWorker, &QThread::started, progress, &QProgressDialog::exec);

    //QObject::connect(progress, &QProgressDialog::canceled, m_TraitementProdCartoWrkr, &Worker::sigAnnuler);

    m_ThreadWorker->start();
}

void MyDialog::StopWork()
{
    m_TraitementProdCartoWrkr->stopWork();
}
4

3 回答 3

2

您发送到工作线程的任何信号都将被排队,因此在所有工作已经完成之后,信号将被处理得太晚。

有(至少)三种方法可以避免这个问题:

  1. 在进行工作时,以常规方式打断您的工作,以便处理传入的信号。例如,您可以QTimer::singleShot(0, ...)在应该恢复工作时向自己发出信号。在任何取消/停止工作信号之后,该信号将位于队列的末尾。显然,这是破坏性的并使您的代码复杂化。

  2. 使用您从 GUI 线程设置但从工作线程读取的状态变量。因此,abool isCancelled默认为 false。一旦这是真的,停止工作。

  3. 有一个控制器对象来管理工作人员/作业并使用锁定。这个对象提供了一个isCancelled()方法可以直接被worker调用。

我以前使用第二种方法,现在在我的代码中使用第三种方法,并且通常将它与进度更新结合起来。每当我发布进度更新时,我也会检查取消标志。原因是我对我的进度更新进行计时,以便它们对用户来说是平滑的,但不会完全阻止工人工作。

对于第二种方法,在您的情况下, m_TraitementProdCartoWrkr 将具有您直接调用的 cancel() 方法(而不是通过信号/插槽),因此它将在调用者的线程中运行,并设置取消标志(您可能会std::atomic混入其中) . GUI/worker 之间的其余通信仍将使用信号和插槽——因此它们在各自的线程中进行处理。

有关第三种方法的示例,请参见此处此处。作业注册表还管理进度(参见此处),并将其进一步发送给监视器(即进度条)。

于 2020-10-26T14:52:50.487 回答
2

看看使用 High-Level QtConcurrent API 重写代码是多么容易:

我的对话.h

#include <QtWidgets/QDialog>
#include "ui_MyDialog.h"

class MyDialog : public QDialog
{
    Q_OBJECT

public:
    MyDialog(QWidget *parent = nullptr);
    ~MyDialog();

    void triggerWork();
    void stopWork();

signals:
    void sigChangeValue(int val);

private:
    Ui::MyDialogClass ui;
};

我的对话.cpp

#include "MyDialog.h"

#include <QtConcurrent/QtConcurrent>
#include <QThread>
#include <atomic>
#include <QProgressDialog>

// Thread-safe flag to stop the thread. No mutex protection is needed 
std::atomic<bool> gStop = false;

MyDialog::MyDialog(QWidget *parent)
    : QDialog(parent)
{
    ui.setupUi(this);

    auto progress = new QProgressDialog;

    connect(this, &MyDialog::sigChangeValue, 
        progress, &QProgressDialog::setValue);

    connect(progress, &QProgressDialog::canceled, 
        this, [this]()
        {
            stopWork();
        }
    );

    // To simplify the example, start the work here:
    triggerWork();
}

MyDialog::~MyDialog()
{ 
    stopWork();
}

void MyDialog::triggerWork()
{
    // Run the code in another thread using High-Level QtConcurrent API
    QtConcurrent::run([this]()
        {
            for(int i = 0; i < 100 && !gStop; i++)
            {
                this->sigChangeValue(i); // signal emition is always thread-safe

                qDebug() << "running... i =" << i;

                QThread::msleep(100);
            }

            qDebug() << "stopped";
        });
}

void MyDialog::stopWork()
{
    gStop = true;
}

另请阅读:

Qt 中的线程基础 Qt 中的
多线程技术
同步线程
线程和对象
关于 C++ 线程中的 Qt 多线程的缺失文章
事件 QObjects

于 2020-10-27T16:01:30.243 回答
1

@ypnos,我感谢您的想法。我为解决问题所做的是修改:

    QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);

Worker构造函数到这一行:

    QObject::connect(progress, &QProgressDialog::canceled, [&]() {
                                                                  this->stopWork();
                                                                 });

现在我可以cancelQProgressDialog.

我不明白的是,为什么第一个代码(下面)不起作用?

    QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);

它不起作用,因为在signals/slots发出信号时选择了连接类型,默认情况下它是Qt::AutoConnection,但我在接收器和发射器之间有不同的线程。(在此处查看更多详细信息),因此无法正常工作

然后我必须指定在发出信号时使用哪种类型的连接来立即调用插槽,因此,这段代码现在也可以工作(主要区别在于,我们明确指定了连接类型Qt::DirectConnection):

    QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork, Qt::DirectConnection);
于 2020-10-27T09:22:56.327 回答