Search code examples
c++multithreadingqtsignals-slots

Issues with Qt's signals and slots behavior with multithreading


I'm currently trying to understand how signals and slots in Qt behave with threads.

I've tried to run a small test with the following code:

class Worker : public QObject{
    Q_OBJECT
public:
    Worker(int _value){value = _value;}
    ~Worker(){};
    int value;

public slots:
    void changeValue(int newValue){value = newValue;}
    void doWork(){
        while(1){
            _sleep(500);
            std::cout << "Value is " << value << std::endl;
            if(value == 0)break;
        }
        emit finished();
    }

signals:
    void finished();
};

class Manager : public QObject{
    Q_OBJECT
public:
    Manager(){}
    ~Manager(){};
signals:
    void modifiedValue(int value);
public:
    void changeTheValue(int value){emit modifiedValue(value);}
};

Basically, the worker display its value member every once in a while, and has a slot with a function that modifies the value.

The manager has the only purpose of emitting a signal with a new value when changeTheValue is called, that maps to the slot in Worker that modifies the value member.

Then I make my Worker class work in a thread the following way :

QThread myThread;
Worker myWorker(10);
Manager manager;

myWorker.moveToThread(&myThread);
QObject::connect(&myThread, SIGNAL(started()), &myWorker,SLOT(doWork()));
QObject::connect(&myWorker, SIGNAL(finished()), &myThread, SLOT(quit()));
QObject::connect(&myWorker, SIGNAL(finished()), &myWorker, SLOT(deleteLater()));
QObject::connect(&myThread, SIGNAL(finished()), &myThread, SLOT(deleteLater()));
QObject::connect(&manager, SIGNAL(modifiedValue(int)), 
                 &myWorker, SLOT(changeValue(int)));
myThread.start();

for(int i = 1; i < 10 ; i++){
    _sleep(1000);
    manager.changeTheValue(i);
}
manager.changeTheValue(0);

But nothing happens. The value doesn't seem to be changed: output show a dozen lines with Value is 10.

What I can't understand is, why does the signal/slot mapping with Manager::modifiedValue and Worker::changeValue does not seem to work ? Is it only because the thread is currently running the doWork()'s loop ? Where does the call to the slots ends up then (queued, discarded, other) ?

I couldn't find much more on how does the signals/slots mechanism work with threads (I only found this thread which explains in which thread's call stack does the call to the slot end up, but the link provided in the answer seems outdated and leads to Qt 5 home).

To sum up the questions:

  • Why does the call to the slot that modifies the value does nothing ?
  • Is it possible to make this work (adding thread-safety wherever necessary) and how ?

Solution

  • There are multiple modes of how signals and slots work with threads (you absolutely must use QThreads for these to work!). These are documented in the manual: http://qt-project.org/doc/qt-4.8/threads-qobject.html#signals-and-slots-across-threads

    The mistake in your code is that the Qt event loop is never called (as doWork never returns). For a repeating call you should be using a timer in that thread. Alternatively (NOT the recommended solution) you can call processEvents in your infinite loop.