Qt 5:在 QThread 工作期间通过信号更新 QProgressBar
Qt 5 : update QProgressBar during QThread work via signal
我正在尝试在处理一些耗时操作的 QThread 的执行过程中更新 QProgressDialog(由 QMainWindow class 拥有)。线程在操作期间发出一些信号,以通知调用应用程序有关进度。我希望将线程发出的进度信号连接到 QProgressDialog 的 setValue 插槽,以便更新进度条。
没用!不显示进度对话框。如果我在我的 QMainWindow 中添加一个插槽并将其连接到工作进度信号以显示线程通过 qDebug 输出给出的值,我看到信号似乎在线程操作期间堆叠并且仅在线程结束时取消堆叠线程。
我尝试了 DirectConnection 连接选项但没有成功。
这是我的代码:
qapp.cpp
#include "qapp.h"
#include <threaded.h>
#include <QVBoxLayout>
#include <QPushButton>
#include <QDebug>
#include <QProgressDialog>
QApp::QApp(QWidget *parent) :
QMainWindow(parent)
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QWidget *window = new QWidget(this);
window->setLayout(mainLayout);
setCentralWidget(window);
QPushButton *button = new QPushButton("Run");
mainLayout->addWidget(button);
connect(button, SIGNAL(clicked(bool)), this, SLOT(doSomeWork()));
}
void QApp::doSomeWork()
{
qDebug() << "do some work";
Threaded worker;
worker.doHeavyCaclulations();
QProgressDialog progressDialog("Copying files...", "Abort Copy", 0, 10000, this);
progressDialog.setWindowModality(Qt::WindowModal);
progressDialog.setMinimumDuration(0);
progressDialog.setValue(0);
connect(&worker, SIGNAL(progress(int)), &progressDialog, SLOT(setValue(int)));
connect(&worker, SIGNAL(progress(int)), this, SLOT(displayProgress(int)));
worker.wait();
qDebug() << "end of thread";
}
void QApp::displayProgress(int value)
{
qDebug() << "data received" << value;
}
QApp::~QApp()
{
}
threaded.cpp :
#include "threaded.h"
#include <QDebug>
Threaded::Threaded(QObject *parent) : QThread(parent)
{
}
void Threaded::doHeavyCaclulations()
{
if (!isRunning())
{
qDebug() << "start thread" ;
start();
}
}
void Threaded::run()
{
qDebug() << "running big loop";
for(double k = 0 ; k < 10000 ; k++)
{
qDebug() << k;
emit progress(k);
}
}
qapp.h
#ifndef QAPP_H
#define QAPP_H
#include <QMainWindow>
class QApp : public QMainWindow
{
Q_OBJECT
public:
explicit QApp(QWidget *parent = 0);
~QApp();
private:
private slots:
void doSomeWork();
void displayProgress(int value);
};
#endif // QAPP_H
threaded.h
#ifndef THREADED_H
#define THREADED_H
#include <QObject>
#include <QThread>
class Threaded : public QThread
{
Q_OBJECT
public:
explicit Threaded(QObject *parent = 0);
void doHeavyCaclulations();
void run();
private:
signals:
void progress(int value);
public slots:
};
#endif // THREADED_H
k < 100 的这段代码的输出是:
do some work
start thread
running big loop
0
1
2
3
[...]
97
98
99
end of big loop
end of thread
data received 17
data received 18
data received 19
[...]
data received 99
如果我更换
worker.wait();
通过
int k=0;
while(worker.isRunning())
{
qDebug() << "main " << k;
k++;
}
我得到了交错的线程输出和调用方法的输出。它确认我的线程独立于调用方法。
知道我做错了什么吗?
QThread
) 的使用绝对错误。参见 what is the correct way to implement a QThread... (example please...)。你需要学习线程的基础知识。
你的错误:
1. 在局部范围内创建一个静态线程对象;
2. 在主线程中等待它完成;
3.不要启动线程;
4.在主线程中直接调用方法doHeavyCaclulations()
;
5. 在没有工作事件循环的情况下发出信号来传递...
为了您的目的,您需要:
不继承QThread
。只需创建具有必要功能的简单 Work
class:
class Work: public QObject
{
Q_OBJECT
public:
Work(){};
virtual ~Work(){};
public slots:
void doHeavyCaclulations() { /* do what you need and emit progress signal */ };
signals:
void progress(int);
}
// Then:
void QApp::doSomeWork()
{
//...
QThread* thread = new QThread(parent);
Work* worker = new Work; // Do not set a parent. The object cannot be moved if it has a parent.
worker->moveToThread(thread);
connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(started()), worker, SLOT(doHeavyCaclulations()));
connect(worker, SIGNAL(progress(int)), &progressDialog, SLOT(setValue(int)));
thread->start();
//...
}
QThread
有一件非常重要的事情,您在使用它时必须始终记住 - 只有 run()
实际上在单独的线程中运行。
每当您创建 QThread
的实例时,此实例的线程亲和性(它所属的线程)就是您在其中创建它的同一线程。这有什么大不了的,它必须做什么您可能会问我的插槽和信号如何处理?嗯,跟这些东西有很大的关系。因为只有 run()
在单独的线程中运行,所以您必须考虑以下几点:
- 信号属于实例 ergo 信号与
run()
具有不同的线程关联
- 插槽属于实例 ergo 插槽与
run()
具有不同的线程关联 - 访问在插槽内和 run()
内处理的共享数据需要明确采用线程安全机制,例如作为互斥量和信号量
- 如果你在插槽中做很多事情,你仍然会冻结你的 UI,就好像你没有使用你的
QThread
也就是说,在某些情况下,您可能希望 to/have 在 QThread
中使用插槽和信号,但此类实现必须针对控制 QThread
的实例而不是它在单独线程中的实际 运行(使用 run()
)。
Here is a small demo 我已经写了一个演示,说明如何使用 QObject
实现槽和信号以及与单独的线程交互。它使用插槽和信号。请注意,实际上没有必要使用 QThread
。例如,您也可以使用 QRunnable
(尽管您必须明确告诉它也继承自 QObject
或使用您创建的 QObject
的单独子类,因为 QRunnable
不支持插槽和信号(它不是 QObject
的子类)。
使用 QObject
的优点是您可以将其实例移动到正在更改其线程关联的线程,以便它完全在该单独的线程(包括插槽)中运行。如果需要,您也可以将多个 QObject
实例放在一个 QThread
中。当继承 QThread
并使用它而不是这个模型时,您的选择会受到很大限制。
所以我的建议是转储 QThread
实现并采用 QThread + QObject
(也称为 Worker 设计模式)做事方式(对于这个特定的场景。
我正在尝试在处理一些耗时操作的 QThread 的执行过程中更新 QProgressDialog(由 QMainWindow class 拥有)。线程在操作期间发出一些信号,以通知调用应用程序有关进度。我希望将线程发出的进度信号连接到 QProgressDialog 的 setValue 插槽,以便更新进度条。
没用!不显示进度对话框。如果我在我的 QMainWindow 中添加一个插槽并将其连接到工作进度信号以显示线程通过 qDebug 输出给出的值,我看到信号似乎在线程操作期间堆叠并且仅在线程结束时取消堆叠线程。
我尝试了 DirectConnection 连接选项但没有成功。
这是我的代码: qapp.cpp
#include "qapp.h"
#include <threaded.h>
#include <QVBoxLayout>
#include <QPushButton>
#include <QDebug>
#include <QProgressDialog>
QApp::QApp(QWidget *parent) :
QMainWindow(parent)
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QWidget *window = new QWidget(this);
window->setLayout(mainLayout);
setCentralWidget(window);
QPushButton *button = new QPushButton("Run");
mainLayout->addWidget(button);
connect(button, SIGNAL(clicked(bool)), this, SLOT(doSomeWork()));
}
void QApp::doSomeWork()
{
qDebug() << "do some work";
Threaded worker;
worker.doHeavyCaclulations();
QProgressDialog progressDialog("Copying files...", "Abort Copy", 0, 10000, this);
progressDialog.setWindowModality(Qt::WindowModal);
progressDialog.setMinimumDuration(0);
progressDialog.setValue(0);
connect(&worker, SIGNAL(progress(int)), &progressDialog, SLOT(setValue(int)));
connect(&worker, SIGNAL(progress(int)), this, SLOT(displayProgress(int)));
worker.wait();
qDebug() << "end of thread";
}
void QApp::displayProgress(int value)
{
qDebug() << "data received" << value;
}
QApp::~QApp()
{
}
threaded.cpp :
#include "threaded.h"
#include <QDebug>
Threaded::Threaded(QObject *parent) : QThread(parent)
{
}
void Threaded::doHeavyCaclulations()
{
if (!isRunning())
{
qDebug() << "start thread" ;
start();
}
}
void Threaded::run()
{
qDebug() << "running big loop";
for(double k = 0 ; k < 10000 ; k++)
{
qDebug() << k;
emit progress(k);
}
}
qapp.h
#ifndef QAPP_H
#define QAPP_H
#include <QMainWindow>
class QApp : public QMainWindow
{
Q_OBJECT
public:
explicit QApp(QWidget *parent = 0);
~QApp();
private:
private slots:
void doSomeWork();
void displayProgress(int value);
};
#endif // QAPP_H
threaded.h
#ifndef THREADED_H
#define THREADED_H
#include <QObject>
#include <QThread>
class Threaded : public QThread
{
Q_OBJECT
public:
explicit Threaded(QObject *parent = 0);
void doHeavyCaclulations();
void run();
private:
signals:
void progress(int value);
public slots:
};
#endif // THREADED_H
k < 100 的这段代码的输出是:
do some work
start thread
running big loop
0
1
2
3
[...]
97
98
99
end of big loop
end of thread
data received 17
data received 18
data received 19
[...]
data received 99
如果我更换 worker.wait(); 通过
int k=0;
while(worker.isRunning())
{
qDebug() << "main " << k;
k++;
}
我得到了交错的线程输出和调用方法的输出。它确认我的线程独立于调用方法。
知道我做错了什么吗?
QThread
) 的使用绝对错误。参见 what is the correct way to implement a QThread... (example please...)。你需要学习线程的基础知识。
你的错误:
1. 在局部范围内创建一个静态线程对象;
2. 在主线程中等待它完成;
3.不要启动线程;
4.在主线程中直接调用方法doHeavyCaclulations()
;
5. 在没有工作事件循环的情况下发出信号来传递...
为了您的目的,您需要:
不继承QThread
。只需创建具有必要功能的简单 Work
class:
class Work: public QObject
{
Q_OBJECT
public:
Work(){};
virtual ~Work(){};
public slots:
void doHeavyCaclulations() { /* do what you need and emit progress signal */ };
signals:
void progress(int);
}
// Then:
void QApp::doSomeWork()
{
//...
QThread* thread = new QThread(parent);
Work* worker = new Work; // Do not set a parent. The object cannot be moved if it has a parent.
worker->moveToThread(thread);
connect(thread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(started()), worker, SLOT(doHeavyCaclulations()));
connect(worker, SIGNAL(progress(int)), &progressDialog, SLOT(setValue(int)));
thread->start();
//...
}
QThread
有一件非常重要的事情,您在使用它时必须始终记住 - 只有 run()
实际上在单独的线程中运行。
每当您创建 QThread
的实例时,此实例的线程亲和性(它所属的线程)就是您在其中创建它的同一线程。这有什么大不了的,它必须做什么您可能会问我的插槽和信号如何处理?嗯,跟这些东西有很大的关系。因为只有 run()
在单独的线程中运行,所以您必须考虑以下几点:
- 信号属于实例 ergo 信号与
run()
具有不同的线程关联
- 插槽属于实例 ergo 插槽与
run()
具有不同的线程关联 - 访问在插槽内和run()
内处理的共享数据需要明确采用线程安全机制,例如作为互斥量和信号量 - 如果你在插槽中做很多事情,你仍然会冻结你的 UI,就好像你没有使用你的
QThread
也就是说,在某些情况下,您可能希望 to/have 在 QThread
中使用插槽和信号,但此类实现必须针对控制 QThread
的实例而不是它在单独线程中的实际 运行(使用 run()
)。
Here is a small demo 我已经写了一个演示,说明如何使用 QObject
实现槽和信号以及与单独的线程交互。它使用插槽和信号。请注意,实际上没有必要使用 QThread
。例如,您也可以使用 QRunnable
(尽管您必须明确告诉它也继承自 QObject
或使用您创建的 QObject
的单独子类,因为 QRunnable
不支持插槽和信号(它不是 QObject
的子类)。
使用 QObject
的优点是您可以将其实例移动到正在更改其线程关联的线程,以便它完全在该单独的线程(包括插槽)中运行。如果需要,您也可以将多个 QObject
实例放在一个 QThread
中。当继承 QThread
并使用它而不是这个模型时,您的选择会受到很大限制。
所以我的建议是转储 QThread
实现并采用 QThread + QObject
(也称为 Worker 设计模式)做事方式(对于这个特定的场景。