【发布时间】:2021-05-16 18:19:00
【问题描述】:
我知道以下不是正常的 Qt 模式,但我想从非主线程创建/引导主线程 GUI 元素。
我们的目标是启动一个独立线程,该线程可以长期存在并做自己的事情。我不想在我的主窗口中为子线程可能做的每件可以想象的 GUI 事情添加钩子。我希望子线程能够显示异步通知,例如,在后台处理带有进度条的事情等。
我创建一个工作对象并将其移动到辅助线程。当线程启动时,它会向工作对象发出信号,以实例化QProgressDialog 的包装器,以模拟辅助线程“做自己的事情”。然后这个包装器被移回主线程,并发布一个事件,导致包装器实际创建QProgressDialog,这发生在主线程中。到目前为止,一切都很好。然后在辅助线程的一个长时间运行的循环中,我发出一个信号,期望它跨越线程边界并更新QProgressDialog。信号似乎确实到达了连接到信号的主循环的 lambda,但它
没有到达移动到主线程的对象。有趣的是,我可以发布一个事件来更新进度,所以我知道事件循环正在运行。我已经尝试了许多默认和排队连接的变体,在移动到新线程之前和之后连接,没有实际的 gui 组件(只是调试文本),基于堆栈与堆对象等。我的理解是 Qt 自动确定是否接收对象与发送对象位于不同的线程中,如果是,则发布由事件循环拾取的事件。问题可能是我在启动子线程后启动了主线程(QApplication::exec())?
在下面的代码中,main.cpp 中的 debug msg 正确地接收到事件并打印了消息,因此信号正在跨越线程边界。它只是没有到达我的包装对象。为什么不呢?
谢谢。
Main.cpp
#include "task.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
TestClass worker("Tc1");
QThread *thr = new QThread;
QObject::connect(thr, &QThread::started, &worker, &TestClass::doWork);
QObject::connect(&worker, qOverload<int,int>(&TestClass::progress), [](int x, int y){
qDebug() << x << "out of" << y;});
QObject::connect(&worker, &TestClass::done, []{qDebug() << "Done";});
worker.moveToThread(thr);
thr->start();
a.exec();
thr->wait();
thr->quit();
qDebug("Done");
}
任务.h
#ifndef TASK_H
#define TASK_H
#include <QApplication>
#include <QEvent>
#include <QDebug>
#include <QObject>
#include <QProgressBar>
#include <QProgressDialog>
#include <QString>
#include <QThread>
#include <functional>
#include <chrono>
static const QEvent::Type CREATE = static_cast<QEvent::Type>(QEvent::User + 0);
static const QEvent::Type SET_VALUE = static_cast<QEvent::Type>(QEvent::User + 1);
// Wraps QProgressDialog
// Create an instance, then move to another thread
class ProgressDialogWrapper : public QObject {
Q_OBJECT
private:
QProgressDialog *mWidget;
QString mLabelText;
QString mCancelButtonText;
int mMinimum;
int mMaximum;
QWidget *mParent;
const Qt::WindowFlags mF;
void create() {mWidget = new QProgressDialog(mLabelText, mCancelButtonText,
mMinimum, mMaximum, mParent, mF);}
public:
ProgressDialogWrapper(const ProgressDialogWrapper &) {}
~ProgressDialogWrapper() override {
qDebug() << "ProgressDialogWrapper destructor";
}
ProgressDialogWrapper(const QString &labelText,
const QString &cancelButtonText,
int minimum,
int maximum,
QWidget *parent = nullptr,
Qt::WindowFlags f = Qt::WindowFlags()) :
mWidget(nullptr),
mLabelText(labelText),
mCancelButtonText(cancelButtonText),
mMinimum(minimum),
mMaximum(maximum),
mParent(parent),
mF(f) {}
ProgressDialogWrapper(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) :
mWidget(nullptr),
mParent(parent),
mF(f){}
QProgressDialog *widget() {return mWidget;}
// Receiving events works across thread boundary
bool event(QEvent *e) override {
switch(e->type()) {
case QEvent::User:
create();
mWidget->show();
return true;
case SET_VALUE:
mWidget->setValue(mWidget->value() + 1);
return true;
default:
return false;
}
}
public slots:
// These slots don't seem to work across thread boundary
void show() {mWidget->show();}
void setMaximum(int x) {mWidget->setMaximum(x);}
void setMinimum(int x) {mWidget->setMinimum(x);}
void setValue(int x) {
qDebug() << "setValue";
mWidget->setValue(x);
}
void requestValue() {emit requestedValue(mWidget->value());}
signals:
void requestedValue(int);
};
// This didn't help
Q_DECLARE_METATYPE(ProgressDialogWrapper)
class TestClass : public QObject {
Q_OBJECT
private:
QString name;
public:
TestClass() {}
TestClass(QString n) : name(n) {}
~TestClass() override {
qDebug() << "TestClass destructor";}
public slots:
void doWork() {
int loopcount = 10;
ProgressDialogWrapper pdw("LabelText", "TheButton", 0, loopcount, nullptr);
pdw.moveToThread(QApplication::instance()->thread());
bool okc = QObject::connect(this, qOverload<int>(&TestClass::progress),
&pdw, &ProgressDialogWrapper::setValue,
Qt::QueuedConnection);
qDebug() << "connect:" << okc;
emit progress(5); // nope this doesn't work
QApplication::postEvent(&pdw, new QEvent(CREATE)); // this does work
std::chrono::milliseconds t(500);
for (int i = 0; i < loopcount; i++) {
// emitting to wrapper doesn't work -- no debug output
emit progress(i);
// emitting to main.cpp lambda does work
emit progress(i, loopcount);
// Posting event works -- progress bar is updated
QApplication::postEvent(&pdw, new QEvent(SET_VALUE));
std::this_thread::sleep_for(t);
QApplication::processEvents(); // no effect, I just made this up in desperation
}
emit done();
}
signals:
void progress(int);
void progress(int, int);
void done();
};
#endif // TASK_H
我也在考虑发送一个QEvent 派生类,它可以将 lambda 作为参数,然后在主线程中运行,但我还没有探索过。
【问题讨论】: