问题描述
我的代码由一个工作程序类和一个对话框类组成。
工人阶级发了一份工作(很长的工作)。
我的对话框类有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();
}
解决方法
您发送到辅助线程的任何信号都将排队,因此在完成所有工作之后,信号将为时已晚。
(至少)有三种方法可以避免此问题:
-
在执行工作时,会定期中断您的工作,以便可以处理传入的信号。例如,您可以使用
QTimer::singleShot(0,...)
通知自己何时应恢复工作。在任何取消/停止的工作信号之后,该信号将在队列末尾。显然,这是破坏性的,并使您的代码复杂化。 -
使用从GUI线程设置但从工作线程读取的状态变量。因此,
bool isCancelled
默认为false。只要这是真的,就停止工作。 -
具有一个控制器对象,该对象管理工作器/作业并使用锁定。该对象提供了一个
isCancelled()
方法,供工作人员直接调用。
我以前使用第二种方法,如今在代码中使用第三种方法,通常将其与进度更新结合使用。每当我发布进度更新时,我也会检查是否已取消标志。原因是我对进度更新进行了计时,以使进度更新对用户来说很平稳,但并没有完全阻止工人上班。
对于第二种方法,在您的情况下,m_TraitementProdCartoWrkr将具有您直接调用的cancel()方法(而不是通过信号/插槽),因此它将在调用者的线程中运行,并设置canceled标志(您可以抛出{ {1}})。 GUI /工作人员之间的其余通信仍将使用信号和插槽-因此它们将在各自的线程中进行处理。
有关第三种方法的示例,请参见here和here。作业注册表还管理进度(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