I have a class derived from QObject
, UploadWorker
, that has been started using the recommended way of running tasks in threads as demonstrated in the Qt documentation.
QThread* thread = new QThread();
UploadWorker* worker = new UploadWorker();
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(doWork()));
Now, that works perfectly fine. My UploadWorker
then tries to start a worker for himself, named Uploader
, using the same technique.
Here are the necessary parts of the headers.
class UploadWorker : public QObject
{
Q_OBJECT
public:
// stuff
public slots:
void doWork();
signals:
void allWorkDone();
protected:
void startUploader();
};
class Uploader : public QObject
{
Q_OBJECT
public:
// stuff
public slots:
void doWork();
void finishWhenQueueIsEmpty();
};
This is the implementation of UploadWorker
.
void
UploadWorker::doWork()
{
// This method is called when QThread emits started()
// Prepare the upload thread
startUploader();
// Do some important work...
// Notify the upload thread that we're done
emit allWorkDone();
}
void
UploadWorker::startUploader()
{
QThread* thread = new QThread();
Uploader* uploader = new Uploader();
uploader->moveToThread(thread);
connect(this, SIGNAL(stopped()), uploader, SLOT(stop()));
connect(uploader, SIGNAL(finished()), thread, SLOT(quit()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(uploader, SIGNAL(finished()), uploader, SLOT(deleteLater()));
connect(thread, SIGNAL(started()), uploader, SLOT(doWork()));
connect(this, SIGNAL(allWorkDone()),
uploader, SLOT(finishWhenQueueIsEmpty()));
thread->start();
}
My problem now is, that the Uploader::finishWhenQueueIsEmpty()
slot is never triggered. Unless: I remove uploader->moveToThread(thread);
.
Where's the bug?
edit
I forgot to mention that the Uploader::doWork()
method is called when the thread starts. It's any other slot, in this example finishWhenQueueIsEmpty()
that don't work.
I fixed a typo regarding the name of the slots.
To connect the signal to the slot, we use QObject::connect(). There are several ways to connect signal and slots. The first is to use function pointers: connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
You can have as many signals you want connected to one slot and vice versa. If several slots are connected to one signal, the slots will be executed one after the other, in the order they have been connected, when the signal is emitted.
It is generally unsafe to provide slots in your QThread subclass, unless you protect the member variables with a mutex. On the other hand, you can safely emit signals from your QThread::run() implementation, because signal emission is thread-safe.
One very probable possibility is that you're executing your Uploader::doWork()
slot and wait for a call to the slot Uploader::finishWhenQueueIsEmpty()
to finish its execution, through some synchronization mechanism (a boolean isRunning
for example).
What you need to do then is connecting your slot like the following:
connect(this, SIGNAL(allWorkDone()),
uploader, SLOT(finishWhenQueueIsEmpty()),
Qt::DirectConnection);
and in finishWhenQueueIsEmpty()
protect the synchro mechanism with a mutex.
Why?
Because the default mode for connection that are made between objects that are in different thread is Qt::QueuedConnection
. What it does here is that every slot of the Uploader
class here are queued for execution, that is, the next slot in the queue will run once the thread returns to the event loop (i.e. once the current slot has finished running). Then, it means that in this mode, calling Uploader::doWork()
and then Uploader::finishWhenQueueIsEmpty()
will result in the program being stuck in doWork()
.
With the Qt::DirectConnection
, the slot will be run in the thread that runs UploaderWorker::doWork()
and can actually be run while Uploader::doWork()
is running.
But then you have two threads that can access the same Uploader
object and thus you need to protect every access/write to parts of this object.
void
UploadWorker::doWork()
{
// This method is called when QThread emits started()
// Prepare the upload thread
startUploader();
// Do some important work...
// Notify the upload thread that we're done
emit allWorkDone();
}
the structure here is telling me that you don't let the event loop run until you are actually done with the "important work", this means that no slots will be triggered.
either let the events run by calling QApplication::processEvents();
periodically or redesign that work so it is processed by a sequence of calls from the event loop.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With