Qt GUI hanging with worker in a QThread - c++

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.

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.

Related

how to connect signals and slots between two QObjects using movetoThread in two threads

I have two classes:A and B,both of them subclass from the QObject.
class A:public QObject
{
Q_OBJECT
public:
A();
~A();
}
i just referred to this article.
Then in the GUI (say the main Thread)I try to exchange data between A and B in different threads using
pA = new A();
QThread *workerThread = new QThread;
pA->moveToThread(workerThread);
connect(workerThread , SIGNAL(started()), pA, SLOT(doWork()));
connect(pA, SIGNAL(finished()), workerThread , SLOT(quit()));
connect(pA, SIGNAL(finished()), pA, SLOT(deleteLater()));
connect(workerThread , SIGNAL(finished()), workerThread , SLOT(deleteLater()));
workerThread ->start();
pB = new B();
connect(pA,SIGNAL(sigProduce(double)),pB,SLOT(slotConsume(double)));//I just don't know where to put this line
QThread *workerThread = new QThread;
pB ->moveToThread(workerThread);
connect(workerThread , SIGNAL(started()), pB , SLOT(doWork()));
connect(pB , SIGNAL(finished()), workerThread , SLOT(quit()));
connect(pB , SIGNAL(finished()), pB , SLOT(deleteLater()));
connect(workerThread , SIGNAL(finished()), workerThread , SLOT(deleteLater()));
workerThread ->start();
sigProduce(double) is a signal defined in the A object and slotConsume(double) is a public slot:
header of class B:
public slots:
void slotConsume(double);
so after the program started obj B can not get the double data from obj A.Does that connect(pA,pB) code's position matters?can anyone help me?THANKS.
Before I go into the details how to solve your problem, make sure the problem is valid in the first place. If the only reason you're using threads is because you want an infinite loop, then maybe you don't need threads at all. If the work inside the loop is light, then you can just do it in the main thread with a QTimer. Read this article to know more about this.
Anyway the problem you have is because you have an infinite loop in your doWork method, which is blocking the thread's event loop. Because of this you had to resort to calling processEvents. However this is not a good design. A better way would be to let the thread go back to the event loop by removing the infinite loop from doWork.
You can then simulate a loop with a timer, and stop the timer when you want to stop the thread. Or you can use QMetaObject::invokeMethod with a Qt::QueuedConnection to call the doWork method from the event loop, and use a bool condition top stop the work. In this example I used the invokeMethod option:
#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>
class Worker : public QObject
{
Q_OBJECT
public:
Worker(QObject *parent = 0) : QObject(parent), _stop(false) {}
public slots:
void doWork()
{
if(_stop)
return;
QThread::sleep(1); // simulating some heavy work here
emit calculationComplete(1564);
QMetaObject::invokeMethod(this, "doWork", Qt::QueuedConnection);
}
void stop()
{
_stop = true;
}
signals:
void calculationComplete(int result);
private:
bool _stop;
};
class ResultHandler : public QObject
{
Q_OBJECT
public:
ResultHandler(QObject *parent = 0) : QObject(parent) {}
public slots:
void handleResult(int result)
{
// do something with result
qDebug() << Q_FUNC_INFO << "Result:" << result;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread thread_a;
Worker worker;
ResultHandler handler;
QObject::connect(&worker, &Worker::calculationComplete, &handler, &ResultHandler::handleResult);
QObject::connect(&thread_a, &QThread::started, &worker, &Worker::doWork);
QObject::connect(&thread_a, &QThread::finished, &worker, &Worker::stop);
worker.moveToThread(&thread_a);
thread_a.start();
QTimer::singleShot(5000, &thread_a, &QThread::quit); // just simulating an exit
int result = a.exec();
thread_a.quit();
thread_a.wait(5000);
return result;
}
#include "main.moc"

start QTimer from another class

I have the following classes:
class MainWindow : public QMainWindow
{
public:
void StartTimer()
{
timer = new QTimer(this);
timer.start(100);
}
private:
QTimer *timer;
};
class AnotherClass
{
public:
MainWindow *window;
void runTimer()
{
window->StartTimer();
}
};
Assuming the window pointer is correctly pointing to the mainwindow, if I try to call runTimer() , I receive this error:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is MainWindow(0x7fff51ffe9f0), parent's thread is QThread(0x7fd1c8d001d0), current thread is QThread(0x7fd1c8f870c0)
QObject::startTimer: Timers can only be used with threads started with QThread
My guess for this error was that since runTimer was being called from a different thread it was also trying to initialize in that same thread? instead of the mainwindow thread?
If I initialize the timer in the default constructor of the main window I receive
QObject::startTimer: Timers cannot be started from another thread
How can I tell a QTimer to start from another class?
You can use signals and slots.
class AnotherClass : public QObject
{
Q_OBJECT
public:
MainWindow * window;
AnotherClass() : window( new MainWindow )
{
// Connect signal to slot (or just normal function, in this case )
connect( this, &AnotherClass::signalStartTimer,
window, &MainWindow::StartTimer,
// This ensures thread safety, usually the default behavior, but it doesn't hurt to be explicit
Qt::QueuedConnection );
runTimer();
}
void runTimer()
{
emit signalStartTimer();
}
signals:
void signalStartTimer();
};

Qt signal slot with threads

I have a problem with signal/slots in a QThread class. My design looks like this:
class Manager : public QObject {
Q_OBJECT
public:
Manager(QObject* parent) : QObject(parent) {
Thread thread* = new Thread(this);
connect(this, SIGNAL(testsignal()), thread, SLOT(test()));
thread->start();
...
emit testsignal();
}
signals:
void testsignal();
};
class Thread : public QThread {
Q_OBJECT
public slots:
void test() {
qDebug() << "TEST";
}
private:
void run() {}
};
The signal never reaches my test() method. Can someone help? Thanks.
The problem is that sending signals across threads results in queuing the signal into the target thread's event queue (a queued connection). If that thread never processes events, it'll never get the signal.
Also, according to the QThread::run documentation:
Returning from this method will end the execution of the thread.
In other words, having an empty run method results in instant termination of the thread, so you're sending a signal to a dead thread.
Signals sent to a QThread object will go to the thread of the parent object. In this case to the same thread that created it.
To have a object live on another thread you should move it to that thread:
class Manager : public QObject {
Q_OBJECT
public:
Manager(QObject* parent) : QObject(parent) {
Thread thread* = new QThread(this);
Receiver* rec = new Receiver(); //no parent
connect(this, SIGNAL(testsignal()), rec, SLOT(test()));
connect(thread, SIGNAL(finished()), rec, SLOT(deleteLater()));
rec->moveToThread(thread);
thread->start();
...
emit testsignal();
}
signals:
void testsignal();
};
class Receiver: public QObject {
Q_OBJECT
public slots:
void test() {
qDebug() << "TEST";
}
};

QThread and QTimer

I'm working on an application developed with Qt 4.6.
I want to create a custom timer that counts in a separate thread. However, I want this timer to be able to send signals to the main thread.
I subclassed QThread but it doesn't seem to work.
Here is Timer.h:
#ifndef TIMER_H
#define TIMER_H
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QTimer>
class Timer : public QThread
{
Q_OBJECT
public:
explicit Timer(QObject *parent = 0);
~Timer();
// true if the timer is active
bool isCounting();
// start the timer with a number of seconds
void startCounting(int value = 300);
void stopCounting();
// the number of seconds to reach
int maximum();
// the current value of the timer
int value();
// elapsed time since the timer has started
int elapsedTime();
signals:
// sent when the timer finishes to count
void timeout();
// an event is emited at each second when the timer is active
void top(int remainingSeconds);
protected:
// launch the thread
//virtual void run();
private slots:
// decrements the remaining time at each second and emits top()
void timerEvent();
private:
QTimer* _timer;
// remaining time
int _left;
// number of seconds at timer startup
int _maximum;
};
#endif // TIMER_H
And Timer.cpp:
#include "Timer.h"
Timer::Timer(QObject *parent) :
QThread(parent)
{
_timer = new QTimer(this);
_maximum = 0;
_left = 0;
connect(_timer, SIGNAL(timeout()), this, SLOT(timerEvent()));
}
Timer::~Timer()
{
delete _timer;
}
bool Timer::isCounting()
{
// test if timer still active
return _timer->isActive();
}
void Timer::startCounting(int value)
{
qDebug() << QString("Start timer for %1 secs").arg(QString::number(value));
if(_left != 0 || _timer->isActive())
{
_timer->stop();
}
_maximum = value;
_left = value;
// emit the first top
emit top(_left);
// start the timer: 1000 msecs
_timer->start(1000);
// start the thread
start();
}
void Timer::stopCounting()
{
qDebug() << QString("Stopping timer at %1 secs => %2 secs remaining.").arg(QString::number(elapsedTime()), QString::number(_left));
// stop timer
_timer->stop();
_left = 0;
_maximum = 0;
// kill thread
terminate();
}
int Timer::maximum()
{
return _maximum;
}
int Timer::value()
{
return _left;
}
void Timer::timerEvent()
{
qDebug() << "Timer event";
if(--_left == 0)
{
// stop timer
_timer->stop();
// emit end of timer
emit timeout();
// stop thread
terminate();
}
else
{
// emit a signal at each second
emit top(_left);
}
}
int Timer::elapsedTime()
{
return (_maximum - _left);
}
EDIT
I realized the object I tried to move to another thread was actually a singleton. It could lead to a problem (see here).
You don't need to subclass QThread in this particular case. And in general, abstain from subclassing QThread unless you are sure it is what you need.
Here is a quick example how to setup a worker and timer in a thread and launch it:
the worker class:
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0) : QObject(parent) {}
signals:
void doSomething();
public slots:
void trigger() {
emit doSomething();
}
};
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MainThreadObject o;
QThread *thread = new QThread;
Worker w;
QTimer timer;
timer.setInterval(1000);
timer.moveToThread(thread);
w.moveToThread(thread);
QObject::connect(thread, SIGNAL(started()), &timer, SLOT(start()));
QObject::connect(&w, SIGNAL(doSomething()), &o, SLOT(doSomething()));
QObject::connect(&timer, SIGNAL(timeout()), &w, SLOT(trigger()));
thread->start();
return a.exec();
}
So, we have the MainThreadObject which represents a QObject derived living in the main thread. We create the timer and Worker object, which is just used to wrap a signal and slot to avoid the need of subclassing QThread. The timer is setup and it and the worker are moved to the new thread, the thread started() signal is connected to the timer start() slot, the worker doSomething() signal is connected to the main thread object doSomething() slot, and finally the timer timeout() signal is connected to the worker trigger() slot. Then the thread is started which initiates the entire chain in the event loop.
As a result, the MainThreadObject::doSomething() is called every second, with the signal emitted from the secondary thread.
Try
QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection); //timer->start()
if you want to start timer immediately
Or
QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection , Q_ARG(int, 1000 )); //timer->start(200)
if you want to start timer after 1000s
In the Non-GUI thread (QThread or Pthread Callback)
First, if you subclass from QThread, you have to implement run() method, if not, there is no point of doing that, you can inherit from QObject instead.
Second, your QTimer has to reside in a thread that runs an event loop. Without an event loop no Qt queued signals can be transmitted. You can launch an event loop by calling exec() in thread's run method:
void Timer::run() {
exec();
}
Probable reason can be, your timer object is not in a thread with event loop. Event loop is required to trigger the signals.
However, I would suggest that you should not go with this approach. Timers use different mechanism on different platform and your code might not behave as expected on different platform.

create qt thread event loop

I am using Qt in order to write a GUI application.
A main thread is responsible for the GUI and creates an QThread in order to do some work with an object.
class Worker
{
void start() {
QTimer* timer = new Timer();
connect(timer,SIGNAL(timeout()),this,SLOT(do()));
}
void do() {
//do some stuff
emit finished();
}
}
class GUI
{
//do some GUI work then call startWorker();
void startWorker() {
QThread* thread = new Thread();
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(start()));
connect(worker, SIGNAL(finished()), workerthread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
}
}
Now I have several problems:
The timer in my worker class does not work. Maybe it is because the new thread has no event loop, but I have no idea how to create such one. I tried
connect(workerthread, SIGNAL(started()), workerthread, SLOT(exec()));
but it does not work either.
When I try to wait on the new thread, the signal is never sent
class GUI
{
void exit() {
thread->wait();
}
}
I think it also is because there is no event loop and because of that no signal is emitted.
Does anybody have an idea how to solve these problems?
why not use qthreadpool, than you make your task class inherits from qrunnable and qobject, this way you can use signals and slots to pass data from one thread to another, is much simpler to implement, and increase performance from not recreating a thread or having one sleeping all the time
class myTask : public QObject, public QRunnable{
Q_OBJECT
protected:
void run(); //where you actually implement what is supposed to do
signals:
void done(int data);//change int to whatever data type you need
}
//moc click example, or use a timer to call this function every x amount of time
void button_click(){
myTask *task = new myTask();
task->setAutoDelete(true);
connect(task,SIGNAL(done(int)),this,SLOT(after_done(int)),Qt::QueuedConnection);
QThreadPool::globalInstance()->start(task);
}
by default you application gets 1 thread automatically, which you can use to handle the graphic, than use the qthreadpool to process the data/object on demand, you can even set the max amount of threads your application can use to process new request, the others will stay in a queue until one thread is freed
QThreadPool::globalInstance()->setMaxThreadCount(5);
this is a sample code for you :
QThread* thread = new QThread();
Worker* worker = new Worker(3000);
worker->moveToThread(thread);
QObject::connect(thread, SIGNAL(started()), worker, SLOT(start()));
thread->start();`
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(qint32,QObject *parent = 0);
qint32 myTime;
signals:
void workFinished();
public slots:
void doWork();
void start();
private:
QTimer *timer;
};
#include "worker.h"
#include <QTimer>
#include <QDebug>
Worker::Worker(qint32 t,QObject *parent) :
QObject(parent)
{
myTime=t;
}
void Worker::start()
{
timer = new QTimer();
timer->start(myTime);
qDebug()<<QString("start work in time:%1").arg(myTime);
connect(timer,SIGNAL(timeout()),this,SLOT(doWork()));
}
void Worker::doWork()
{
qDebug()<<"dowork";
timer->stop();
emit workFinished();
}
Debug results :
start work in time:3000
I hope this helps you.