如何使用 QProgressDialog 的取消按钮 stop/cancel 工人作业
How to stop/cancel a worker job using the cancel button of a QProgressDialog
我的代码由一个工人 class 和一个对话框 class 组成。
worker class 启动了一个工作(一个很长的工作)。
我的对话框 class 有 2 个按钮,允许启动和停止作业(它们工作正常)。
我想实现一个忙碌的酒吧,表明工作正在进行中。
我在 Worker class 中使用了 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, 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();
}
您发送到工作线程的任何信号都将排队,因此在所有工作都已完成之后,信号的处理为时已晚。
有(至少)三种方法可以避免这个问题:
在进行工作时,以常规方式中断您的工作,以便处理传入的信号。例如,您可以使用 QTimer::singleShot(0, ...)
来通知自己何时应该恢复工作。然后,此信号将位于队列的末尾,在任何 canceled/stop 工作信号之后。显然,这是破坏性的,会使您的代码复杂化。
使用您从 GUI 线程设置但从工作线程读取的状态变量。因此,默认为 false 的 bool isCancelled
。一旦属实,立即停止工作。
有一个控制器对象来管理工作人员/作业并使用锁定。这个对象提供了一个isCancelled()
方法,可以被worker直接调用。
我以前使用第二种方法,现在在我的代码中使用第三种方法,并且通常将它与进度更新结合起来。每当我发布进度更新时,我也会检查取消标志。原因是我对我的进度更新进行了计时,以便它们对用户来说是流畅的,但不会完全阻止工人工作。
对于第二种方法,在您的情况下,m_TraitementProdCartoWrkr 将有一个您直接调用的 cancel() 方法(而不是通过 signal/slot),因此它将 运行 在调用者的线程,并设置取消标志(你可以将 std::atomic
扔到混合中)。 GUI/worker 之间的其余通信仍将使用信号和槽——因此它们在各自的线程中进行处理。
有关第三种方法的示例,请参阅 here and here. The job registry also manages progress (see here),并将其进一步发送给监视器(即进度条)。
@ypnos,谢谢你的想法。
我为解决问题所做的是修改:
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);
从 Worker
构造器到这一行:
QObject::connect(progress, &QProgressDialog::canceled, [&]() {
this->stopWork();
});
现在我可以通过 QProgressDialog
的 cancel
按钮停止作业。
我不明白的是,为什么第一个代码(下面)不起作用?
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);
它没有工作,因为在发出信号时选择了 signals/slots
的连接类型,默认情况下是 Qt::AutoConnection
,但我在接收器和发射器之间有不同的线程。
(查看更多详情here),因此无法正常工作
然后我必须指定在发出信号时使用哪种连接类型来立即调用插槽,因此,这段代码现在也可以工作(主要区别在于,在这里,我们明确指定连接类型 Qt::DirectConnection
):
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork, Qt::DirectConnection);
看看使用 High-Level 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
我的代码由一个工人 class 和一个对话框 class 组成。
worker class 启动了一个工作(一个很长的工作)。
我的对话框 class 有 2 个按钮,允许启动和停止作业(它们工作正常)。
我想实现一个忙碌的酒吧,表明工作正在进行中。
我在 Worker class 中使用了 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, 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();
}
您发送到工作线程的任何信号都将排队,因此在所有工作都已完成之后,信号的处理为时已晚。
有(至少)三种方法可以避免这个问题:
在进行工作时,以常规方式中断您的工作,以便处理传入的信号。例如,您可以使用
QTimer::singleShot(0, ...)
来通知自己何时应该恢复工作。然后,此信号将位于队列的末尾,在任何 canceled/stop 工作信号之后。显然,这是破坏性的,会使您的代码复杂化。使用您从 GUI 线程设置但从工作线程读取的状态变量。因此,默认为 false 的
bool isCancelled
。一旦属实,立即停止工作。有一个控制器对象来管理工作人员/作业并使用锁定。这个对象提供了一个
isCancelled()
方法,可以被worker直接调用。
我以前使用第二种方法,现在在我的代码中使用第三种方法,并且通常将它与进度更新结合起来。每当我发布进度更新时,我也会检查取消标志。原因是我对我的进度更新进行了计时,以便它们对用户来说是流畅的,但不会完全阻止工人工作。
对于第二种方法,在您的情况下,m_TraitementProdCartoWrkr 将有一个您直接调用的 cancel() 方法(而不是通过 signal/slot),因此它将 运行 在调用者的线程,并设置取消标志(你可以将 std::atomic
扔到混合中)。 GUI/worker 之间的其余通信仍将使用信号和槽——因此它们在各自的线程中进行处理。
有关第三种方法的示例,请参阅 here and here. The job registry also manages progress (see here),并将其进一步发送给监视器(即进度条)。
@ypnos,谢谢你的想法。 我为解决问题所做的是修改:
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);
从 Worker
构造器到这一行:
QObject::connect(progress, &QProgressDialog::canceled, [&]() {
this->stopWork();
});
现在我可以通过 QProgressDialog
的 cancel
按钮停止作业。
我不明白的是,为什么第一个代码(下面)不起作用?
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);
它没有工作,因为在发出信号时选择了 signals/slots
的连接类型,默认情况下是 Qt::AutoConnection
,但我在接收器和发射器之间有不同的线程。
(查看更多详情here),因此无法正常工作
然后我必须指定在发出信号时使用哪种连接类型来立即调用插槽,因此,这段代码现在也可以工作(主要区别在于,在这里,我们明确指定连接类型 Qt::DirectConnection
):
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork, Qt::DirectConnection);
看看使用 High-Level 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