Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt 4.8 Signals/Slots not called after moveToThread()

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.

like image 204
Robert Lohr Avatar asked Aug 12 '14 14:08

Robert Lohr


People also ask

How do I connect Qt signals and 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);

How many signals can be connected to a slot in Qt?

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.

Are Qt signals and slots thread safe?

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.


2 Answers

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.

like image 188
JBL Avatar answered Oct 16 '22 09:10

JBL


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.

like image 43
ratchet freak Avatar answered Oct 16 '22 09:10

ratchet freak