I've been having some problems with using QThreads
which made me explore different combinations before I've found the right one. However I still don't fully understand what is really happening in the four cases shown below when it comes to event loops and signal-slot processing.
I added some comments to OUTPUT section, but as you can see I'm not sure if my assumptions about what caused observed behaviors are correct. Also I'm not sure if case 3
is something that might be used in real code. Here is my test code (only the main.cpp
differs for each case):
worker.h:
#include <QObject>
#include <QDebug>
#include <QThread>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0) { this->isRunning_ = false;}
bool isRunning() const { return isRunning_; }
signals:
void processingFinished();
void inProgress();
public slots:
void process()
{
this->isRunning_ = true;
qDebug() << this << "processing started";
for (int i = 0; i < 5; i++)
{
QThread::usleep(1000);
emit this->inProgress();
}
qDebug() << this << "processing finished";
this->isRunning_ = false;
emit this->processingFinished();
}
private:
bool isRunning_;
};
workermanager.h:
#include "worker.h"
class WorkerManager : public QObject
{
Q_OBJECT
public:
explicit WorkerManager(QObject *parent = 0) :
QObject(parent) {}
public slots:
void process()
{
QThread *thread = new QThread();
Worker *worker = new Worker();
connect(thread,SIGNAL(started()),worker,SLOT(process()));
connect(worker,SIGNAL(processingFinished()),this,SLOT(slot1()));
connect(worker,SIGNAL(inProgress()),this,SLOT(slot2()));
worker->moveToThread(thread);
qDebug() << "starting";
thread->start();
QThread::usleep(500);
while(worker->isRunning()) { }
qDebug() << "finished";
}
void slot1() { qDebug() << "slot1"; }
void slot2() { qDebug() << "slot2"; }
};
main.cpp (case 1 - no separate thread for
workerManager
):
#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerManager* workerManager = new WorkerManager;
workerManager->process();
qDebug() << "end";
return a.exec();
}
OUTPUT - both
slot1
andslot2
called ata.exec()
(??? - using main event loop?):
starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
slot2
slot2
slot2
slot2
slot2
slot1
main.cpp (case 2 -
workerManager
moved to separate thread, but thread not started):
#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
workerManager->process();
qDebug() << "end";
return a.exec();
}
OUTPUT - neither
slot1
norslot2
was called - (??? event loop associated with thread receives signals but since thread was not started slots are not called?):
starting
Worker(0x112db20) processing started
Worker(0x112db20) processing finished
finished
end
main.cpp (case 3 -
workerManager
moved to separate thread, thread started butworkerManager::process()
called viaworkerManager->process()
):
#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
thread->start();
workerManager->process();
qDebug() << "end";
return a.exec();
}
OUTPUT -
slot2
called whileWorker
still executing itsprocess()
(???):
starting
Worker(0x197bb20) processing started
slot2
slot2
slot2
slot2
Worker(0x197bb20) processing finished
finished
end
slot2
slot1
main.cpp (case 4 -
workerManager
moved to separate thread, thread started butworkerManager::process()
called usingstarted()
signal fromthread
):
#include <QCoreApplication>
#include "workermanager.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerManager* workerManager = new WorkerManager;
QThread *thread = new QThread();
workerManager->moveToThread(thread);
QObject::connect(thread,SIGNAL(started()),workerManager,SLOT(process()));
thread->start();
qDebug() << "end";
return a.exec();
}
OUTPUT - all events processed after reaching
a.exec()
(???):
end
starting
Worker(0x7f1d700013d0) processing started
Worker(0x7f1d700013d0) processing finished
finished
slot2
slot2
slot2
slot2
slot2
slot1
Thanks for any clarifications.
All results you got are perfectly correct. I'll try to explain how this works.
An event loop is an internal loop in Qt code that processes system and user events. Event loop of main thread is started when you call a.exec()
. Event loop of another thread is started by default implementation of QThread::run
.
When Qt decides it's time to process an event, it executes its event handler. While event handler is working, Qt has no chance to process any other event (unless given directly by QApplication::processEvents()
or some other methods). Once the event handler is finished, control flow returns to the event loop and Qt may execute another handler to process another event.
Signals and slots are not the same as events and event handlers in Qt terminology. But slots are handled by event loops somewhat similarily. If you have control flow in your code(such as in main
function) you can execute any slot immediately just as any other C++ function. But when Qt does that it can only do that from an event loop. It should be noted that signals are always sent immediately, while slot execution may be delayed.
Now let's see what happens in each case.
WorkerManager::process
is executed directly at the program start. New thread is started and Worker::process
is executed immediately in the new thread. WorkerManager::process
continues execution until Worker is done, freezing all other actions (including slot processing) in main thread. After WorkerManager::process
is finished, control flow goes to QApplication::exec
. Qt establishes connection to the other thread, receives messages about slot invokation and invokes all of them consequently.
Qt by default executes slots of an object in the thread this object belongs to. Main thread will not execute slots of WorkerManager
because it belongs to another thread. However this thread is never started. Its event loop is never finished. Invokations of slot1
and slot2
are left forever in Qt's queue waiting for you to start the thread. Sad story.
In this case WorkerManager::process
is executed in main thread because you invoke it directly from main thread. Meanwhile, WorkerManager
's thread is started. Its event loop is launched and waiting for events. WorkerManager::process
starts Worker
's thread and executes Worker::exec
in it. Worker
starts sending signals to WorkerManager
. WorkerManager
's thread almost immediately starts to execute appropriate slots. At this point it seems awkward that WorkerManager::slot2
and WorkerManager::process
are executed simultaneously. But it's perfectly fine, at least if WorkerManager
is thread-safe. Shortly after Worker
is done, WorkerManager::process
is finished and a.exec()
is executed but has not much to process.
Main function just launches WorkerManager
's thread and immediately goes to a.exec()
, resulting in end
as first line in the output. a.exec()
processes something and ensures program execution but doesn't execute WorkerManager
's slots because it belongs to another thread. WorkerManager::process
is executed in WorkerManager
's thread from its event loop. Worker
's thread is started and Worker::process
starts sending signals from Worker
's thread to WorkerManager
's thread. Unfortunately the latter is busy executing WorkerManager::process
. When Worker
is done, WorkerManager::process
also finishes and WorkerManager
's thread immediately executes all queued slots.
The largest problem in your code is usleep
and infinite loops. You should almost never use those when working with Qt. I understand that a sleep in Worker::process
is just a placeholder for some real calculation. But you should remove sleep and infinite loop from WorkerManager
. Use WorkerManager::slot1
to detect Worker
's termination. If you develop a GUI application there would be no need to move WorkerManager
to another thread. All its methods (without sleep) will be executed fast and will not freeze the GUI.