How to emit cross-thread signal in Qt?
Asked Answered
P

3

63

Qt documentation states that signals and slots can be direct, queued and auto.

It also stated that if object that owns slot 'lives' in a thread different from object that owns signal, emitting such signal will be like posting message - signal emit will return instantly and slot method will be called in target thread's event loop.

Unfortunately, documentation do not specify that 'lives' stands for and no examples is available. I have tried the following code:

main.h:

class CThread1 : public QThread
{
Q_OBJECT
public:
    void run( void )
    {
        msleep( 200 );
        std::cout << "thread 1 started" << std::endl;
        MySignal();
        exec();
    }
signals:
    void MySignal( void );
};

class CThread2 : public QThread
{
Q_OBJECT
public:
    void run( void )
    {
        std::cout << "thread 2 started" << std::endl;
        exec();
    }
public slots:
    void MySlot( void )
    {
        std::cout << "slot called" << std::endl;
    }
};

main.cpp:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    CThread1 oThread1;
    CThread2 oThread2;
    QObject::connect( & oThread1, SIGNAL( MySignal() ),
        & oThread2, SLOT( MySlot() ) );
    oThread1.start();
    oThread2.start();
    oThread1.wait();
    oThread2.wait();
    return a.exec();
}

Output is:

thread 2 started
thread 1 started

MySlot() is never called :(. What I'm doing wrong?

Phlebosclerosis answered 12/3, 2009 at 11:38 Comment(0)
N
50

There are quite a few problems with your code :

  • like said by Evan the emit keyword is missing
  • all your objects live in the main thread, only the code in the run methods live in other threads, which means that the MySlot slot would be called in the main thread and I'm not sure that's what you want
  • your slot will never be called since the main event loop will never been launched : your two calls to wait() will only timeout after a very long time (and you'll probably kill your application before that happens) and I don't think that's what you want either, anyway they really have no use in your code.

This code would most likely work (though I have not tested it) and I think it does what you want it to do :

class MyObject : public QObject
{
    Q_OBJECT
public slots:
    void MySlot( void )
    {
        std::cout << "slot called" << std::endl;
    }
};

class CThread1 : public QThread
{
    Q_OBJECT
public:
    void run( void )
    {
        std::cout << "thread 1 started" << std::endl;
        int i = 0;
        while(1)
        {
           msleep( 200 );
           i++;
           if(i==1000)
              emit MySignal();
        }
    }
signals:
    void MySignal( void );
};

class CThread2 : public QThread
{
    Q_OBJECT
public:
    void run( void )
    {
        std::cout << "thread 2 started" << std::endl;
        exec();
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    CThread1 oThread1;
    CThread2 oThread2;
    MyObject myObject;
    QObject::connect( & oThread1, SIGNAL( MySignal() ),
        & myObject, SLOT( MySlot() ) );
    oThread2.start();
    myObject.moveToThread(&oThread2)
    oThread1.start();
    return a.exec();
}

Now MyObject will live in thread2 (thanks to moveToThread).

MySignal should be sent from thread1 (thought I'm not sure on that one, it might be sent from main thread, it doesn't really matter).

No event loop is needed in thread1 since emitting a signal doesn't need an event loop. An event loop is needed in thread2 (lanched by exec()) to receive the signal.

MySlot will be called in thread2.

Nashoma answered 12/3, 2009 at 13:49 Comment(5)
is it any way to connect slot to signal before threads are started? I really don't want threads to emit something before all is connected.Phlebosclerosis
In my example the connection is done before the thread doing the emit is started. I'm waiting until 1000 just for the fun of it ;)Nashoma
Thanks, moveToThread works before any thread is started. of course myThread.moveToThread( & myThread ); looks a bit weird, but works just fine.Phlebosclerosis
Strictly speaking, whether or not they use the emit "keyword" shouldn't affect anything. It is #defined to an empty macro.Tribade
What if the signal is emitted from a non-qt thread (i.e. say a thread created with boost), is that a problem or is it even possible? For example, if I have a non-qt network server class that can invoke callbacks from its internal thread when receiving messages, and I want to it in a QT project and route those callbacks to QT slots.Constancy
A
40

Do not subclass QThread for Qt 4.4+

While Aiua's answer is good, I want to point out some issues with QThread and Qt 4.6 or 4.7.

This article sums it up: http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/

Lack of Documentation on Qt's part

Unfortunately the problem stems from a lack of updates to documentation. Prior to Qt 4.4 QThread had no default run() implementation, which meant that you had to subclass QThread in order to use it.

If you're using Qt 4.6 or 4.7 then you almost certainly should not subclass QThread.

Use moveToThread

The key to getting slots to execute in a worker thread is to use the moveToThread method as Aiua pointed out.

Atonement answered 2/12, 2011 at 17:24 Comment(2)
Subclassing QThread and overriding run() is supported by Qt. It is even the first suggested solution by Qt on this table. However, this should only be used when no event loop is required for the thread. Especially, if someone need to call moveToThread(this); inside the thread as shown in the linked article, he is using it wrongly.Sidoon
That was the official recommendation, until this article came out: woboq.com/blog/qthread-you-were-not-doing-so-wrong.html I've implemented threading this way, and it is very lightweight. Stable as a tank. No moveToThread. No extra QObjects allocated. You can indeed put worker code in the run() function (or call it from there). Now, they officially support it.U
S
0

you should emit the signal to start your thread function like

emit operateCut(examId,examName_examTemplate[examName].studentIdRec,examName_examTemplate[examName].choiceRecA,examName_examTemplate[examName].choiceRecB,examName_examTemplate[examName].objectRecA,examName_examTemplate[examName].objectRecB);

you can add more than one argument in this signal

Schwing answered 22/12, 2018 at 3:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.