如何使用QProgressDialog的“取消”按钮停止/取消工人作业

问题描述

我的代码一个工作程序类和一个对话框类组成。 工人阶级发了一份工作(很长的工作)。 我的对话框类有2个按钮,可用于启动和停止作业(它们可以正常工作)。 我想开设一个忙碌的酒吧,表明工作正在进行中。 我在Worker类中使用了QProgressDialog。当我想使用QprogressDialog cancel按钮停止工作时,无法捕获信号&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);
    QProgressDialog* progress = new QProgressDialog("do Work","Annuler",0);
    progress->setMinimumDuration(0);
    QObject::connect(this,&Worker::sigChangeValue,progress,&QProgressDialog::setValue);
    QObject::connect(this,&Worker::sigFinished,&QProgressDialog::close);
    QObject::connect(this,&Worker::sigAnnuler,&QProgressDialog::cancel);
    QObject::connect(progress,&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,&MyDialog::triggerWork);
    connect(ui->Cancel,&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),&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,&Worker::doWork);
    //QObject::connect(m_ThreadWorker,&QProgressDialog::exec);

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

    m_ThreadWorker->start();
}

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

解决方法

您发送到辅助线程的任何信号都将排队,因此在完成所有工作之后,信号将为时已晚。

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

  1. 在执行工作时,会定期中断您的工作,以便可以处理传入的信号。例如,您可以使用QTimer::singleShot(0,...)通知自己何时应恢复工作。在任何取消/停止的工作信号之后,该信号将在队列末尾。显然,这是破坏性的,并使您的代码复杂化。

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

  3. 具有一个控制器对象,该对象管理工作器/作业并使用锁定。该对象提供了一个isCancelled()方法,供工作人员直接调用。

我以前使用第二种方法,如今在代码中使用第三种方法,通常将其与进度更新结合使用。每当我发布进度更新时,我也会检查是否已取消标志。原因是我对进度更新进行了计时,以使进度更新对用户来说很平稳,但并没有完全阻止工人上班。

对于第二种方法,在您的情况下,m_TraitementProdCartoWrkr将具有您直接调用的cancel()方法(而不是通过信号/插槽),因此它将在调用者的线程中运行,并设置canceled标志(您可以抛出{ {1}})。 GUI /工作人员之间的其余通信仍将使用信号和插槽-因此它们将在各自的线程中进行处理。

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

,

看看使用高级QtConcurrent API重写代码有多么容易:

MyDialog.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;
};

MyDialog.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;
}

另请参阅:

Threading Basics in Qt
Multithreading Technologies in Qt
Synchronizing Threads
Threads and Objects
The Missing Article About Qt Multithreading in C++
Threads Events QObjects

,

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

    QObject::connect(progress,&Worker::stopWork);

Worker构造函数到此行:

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

现在,我可以通过cancel的{​​{1}}按钮停止工作了。

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

QProgressDialog

之所以不起作用,是因为发出信号时选择了 QObject::connect(progress,&Worker::stopWork); 的连接类型,默认情况下为signals/slots,但是接收器和发射器之间的线程不同。 (查看更多详细信息here),因此无法正常工作

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

Qt::AutoConnection