I have a worker which needs to complete an arbitrary blocking task
class Worker : public QObject
{
Q_OBJECT;
public:
using QObject::QObject;
public slots:
void start()
{
for (int i = 0; i < 10; i )
{
qDebug("I'm doing work...");
Sleep(1000);
}
}
};
In my MainWindow
constructor, I start the task like so:
class MainWindow : public QWidget
{
Q_OBJECT;
public:
explicit MainWindow(QWidget* parent = nullptr) : QWidget(parent)
{
QThread* t = new QThread(this);
Worker* w = new Worker(this);
w->moveToThread(t);
this->connect(
t, &QThread::started,
w, &Worker::start
);
this->connect(
t, &QThread::finished,
t, &QThread::deleteLater
);
t->start();
}
};
And my entry point:
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MainWindow mainWindow{};
mainWindow.show();
return app.exec();
}
When I run the program, I just get a spinning mouse circle, and the window contents don't load until 10 seconds later (when the worker is complete). Why is this so? What do I need to do so that Worker::start
is run in the background without affecting the GUI?
P.S. I am using Qt 6.2.2 and Windows 11, but I highly doubt that has anything to do with this issue.
CodePudding user response:
Simply:
class MainWindow : public QWidget
{
Q_OBJECT;
public:
explicit MainWindow(QWidget* parent = nullptr) : QWidget(parent)
{
QThread* t = new QThread(); // delete this
Worker* w = new Worker(); // delete this
w->moveToThread(t);
this->connect(
t, &QThread::started,
w, &Worker::start
);
this->connect(
t, &QThread::finished,
t, &QThread::deleteLater
);
t->start();
}
};
It gives a reasonable explanation.
CodePudding user response:
If you check your output, you're very likely to find a line like this:
QObject::moveToThread: Cannot move objects with a parent
To prove you can't, just add some debug lines:
QThread* t = new QThread(this);
Worker* w = new Worker( this);
w->moveToThread(t);
qDebug() << "CURRENT: " << QThread::currentThread();
qDebug() << "WORKER : " << w->thread();
Worker
's thread is still the current one, after trying to move it into t
...
I would recommend to make both objects (QThread and Worker) private members of your MainWindow class: no reason to use pointers, here:
class MainWindow : public QWidget
{
Q_OBJECT;
QThread _thread;
Worker _worker;
public:
explicit MainWindow(QWidget* parent = nullptr) : QWidget(parent)
{
_worker.moveToThread(t);
connect(
&_thread, &QThread::started,
&_worker, &Worker::start
);
_thread.start();
}
};
CodePudding user response:
The problem is that the worker is actually not moved to thread (Isn't a warning written to console? I bet it is.), because its parent, i.e. MainWindow
instance is still in the GUI thread.
When moving to thread, you can only move the whole hierarchy of objects by moving the very top parent. You cannot have parent in different thread than its children.
You should create the worker without parent.
Worker* w = new Worker();
Of course you should also add some finished()
signal to your Worker
class.
class Worker : public QObject
{
Q_OBJECT;
public:
using QObject::QObject;
void start()
{
for (int i = 0; i < 10; i )
{
qDebug("I'm doing work...");
Sleep(1000);
}
emit finished();
}
signals:
void finished();
};
and add these connections:
this->connect(
w, &Worker::finished,
t, &QThread::quit
);
this->connect(
t, &QThread::finished,
w, &Worker::deleteLater
);
otherwise your thread's even loop will not end and the worker will not be deleted.