我的代码由一个工作者类和一个对话框类组成。工人阶级开始了一项工作(一项很长的工作(。我的对话框类有两个按钮,允许启动和停止作业(它们工作正常(。我想实现一个繁忙的酒吧,显示一项工作正在进行中。我在Worker类中使用了QProgressDialog。当我想使用QprogressDialogcancel
按钮停止作业时,我无法捕捉到信号&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, ...)
来通知自己何时应该恢复工作。然后,在任何取消/停止工作信号之后,该信号将位于队列的末尾。显然,这会破坏您的代码并使其复杂化。 -
使用从GUI线程设置但从工作线程读取的状态变量。因此,默认为false的
bool isCancelled
。一旦这是真的,就停止工作。 -
有一个控制器对象来管理工作人员/作业并使用锁定。这个对象提供了一个
isCancelled()
方法,由worker直接调用。
我以前使用第二种方法,现在在代码中使用第三种方法,通常将其与进度更新相结合。每当我发布进度更新时,我也会检查取消的标志。原因是我对进度更新进行计时,这样用户就可以很顺利地进行更新,但不会完全阻止工人工作。
对于第二种方法,在您的情况下,m_TraitementProdCartoWrkr将有一个您直接调用的cancel((方法(而不是通过signal/slot(,因此它将在调用者的线程中运行,并设置cancell标志(您可以将std::atomic
放入混合中(。GUI/worker之间的其余通信仍将使用信号&插槽--因此它们在各自的线程中进行处理。
有关第三种方法的示例,请参见此处和此处。作业注册表还管理进度(请参阅此处(,并将其进一步发送给监视器(即进度条(。
查看使用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;
}
另请阅读:
Qt中的线程基础知识
Qt中多线程技术
同步线程
线程和对象
C++中缺少的关于Qt多线程的文章
Threads事件QObjects
@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
,但我在接收器和发射器之间有不同的线程。(请参阅此处的更多详细信息(,因此它无法工作
然后,我必须指定在信号发出时使用哪种类型的连接来立即调用插槽,因此,此代码现在也可以工作(主要区别是,在这里,我们明确指定了连接类型Qt::DirectConnection
(:
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork, Qt::DirectConnection);