Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

signals and slots across threads without QThread inheritance



  • Dear Qt Forum,

    currently I am developing a hobby project with Qt and wanted to use signals and slots across threads (without QThread inheritance).
    Sorry for bringing up this topic again, but I am a bit stuck to create signals and slots across threads with this elegant method explained by Maya Posch (thanks to her, by the way!) MoveToThread MayaPosch or described in the last code box of QtWiki QtWiki.

    I would appreciate any help from you in the following question:
    How can I achieve that a signal sent from main application thread is picked up by a slot of a class moved to a second thread via class->moveToThread(thread)?
    Is it because of the missing event loop in the second thread? See: QtForum:Topic66025

    Here you can find a code example how far I am with my implementation of the above idea ( I know, thread is not terminated properly.)

    main.cxx:

    #include <QApplication>
    #include <QThread>
    #include <iostream>
    #include "A.h"
    #include "B.h"
    int main(int argc , char *argv[]){
      QApplication app(argc, argv);
      //doing something for gui and user ...
      // ...
      std::cout<<"Hello Qt Forum."<<std::endl;
      // ...
      A a(1);
      B b(2);
      //creating thread 
      QThread* thread = new QThread;
      a.moveToThread(thread); // change thread affinity
      bool check1;
      check1 = QObject::connect(thread, SIGNAL(started()), &a, SLOT(processList()), Qt::AutoConnection);
      std::cout<<"successful connection?: check1= "<<check1<<std::endl;
    
      //starting thread
      thread->start();
      
      //now: give an update from B to A:
      b.updater(42); // --> currently not working, why?
      return app.exec();
    }
    
    

    class A:

    #ifndef A_H
    #define A_H
    #include <QObject>
    #include <QList>
    class A : public QObject{
      Q_OBJECT
    public:
      A( int id );
    public slots:
      void processList();
      void makeUpdate( int update );
    private:
      int name;
      QList<int> list;
      QList<int> buffer;
      bool running = true;
    };
    #endif
    
    
    #include "A.h"
    #include <iostream>
    //
    A::A (int id){
      name = id;
    }
    
    void A::processList(){
      int i = 0;
      while(running){
        if( i < list.size() and !list.isEmpty() ){
          //doing something that should not block main.cxx
          std::cout<<list.at(i);
          i++;
          if( i == list.size() ){
    	//end of list, reset list index
    	i = 0;
          }
          list.append(buffer);
        }
      }
    }
    
    void A::makeUpdate( int update ){
      std::cout<<"Updating..."<<std::endl;
      buffer.append(update);
    }
    
    
    

    class B:

    #ifndef B_H
    #define B_H
    #include <QObject>
    class B : public QObject{
      Q_OBJECT
    public:
      B( int id );
    signals:
      void updater( int update );
      private:
      int name;
    };
    #endif
    
    #include "B.h"
    #include <iostream>
    //
    B::B (int id){
      name = id;
    }
    
    

    output:

    Hello Qt Forum.
    successful connection?: check1= 1
    

    I am looking forward to your answers and:
    Thank you in advance!
    Cheers,
    Daniel



  • @dan13l said in signals and slots across threads without QThread inheritance:

    Object::connect(&b, SIGNAL(updater(int)), &a, SLOT(makeUpdate(int)), Qt::AutoConnection);

    • First: I would also suggest you to use new Qt connect syntax, to enable connection check at build time
    • Second: always use emit when calling a signal, this made your code easier to understand
    • Third: I would suggest you to use QueuedConnection to ensure signal will be emitted when event loop is running
    Object::connect(&b, &B::updater , &a, &A::makeUpdate, Qt::QueuedConnection);
    


  • @dan13l said in signals and slots across threads without QThread inheritance:

    b.updater(42); // --> currently not working, why?

    As far as I can see updater is a signal of class B and you did not connection this signal from instance b to anything.
    So it do nothing!
    What did you expect?



  • Thank you @KroMignon for your reply.
    Uh, of course, I do not expect anything, you are right. Stupid mistake from my side when creating this example as condensed version of my original code.

    Of course, I have to add this to main.cxx:

    Object::connect(&b, SIGNAL(updater(int)), &a, SLOT(makeUpdate(int)), Qt::AutoConnection);
    


  • @dan13l said in signals and slots across threads without QThread inheritance:

    Object::connect(&b, SIGNAL(updater(int)), &a, SLOT(makeUpdate(int)), Qt::AutoConnection);

    • First: I would also suggest you to use new Qt connect syntax, to enable connection check at build time
    • Second: always use emit when calling a signal, this made your code easier to understand
    • Third: I would suggest you to use QueuedConnection to ensure signal will be emitted when event loop is running
    Object::connect(&b, &B::updater , &a, &A::makeUpdate, Qt::QueuedConnection);
    


  • This post is deleted!


  • @KroMignon Awesome!!
    Thank you so much for your help! I really appreciate that! =)

    Point one and three of your suggestion did the job!
    So, what I learned is to explicitly set QueuedConnection together with the new syntax, because I tried again AutoConnection and then it fails to work.

    Just as a documentation, below are the corrected and now working code lines:

    main.cxx

    #include <QApplication>
    #include <QThread>
    #include <iostream>
    #include "A.h"
    #include "B.h"
    int main(int argc , char *argv[]){
      QApplication app(argc, argv);
      //doing something for gui and user ...
      // ...
      std::cout<<"Hello Qt Forum."<<std::endl;
      // ...
      A a(1);
      B b(2);
      //creating thread 
      QThread* thread = new QThread;
      a.moveToThread(thread); // change thread affinity
      bool check1, check2;
      check1 = QObject::connect(thread, &QThread::started, &a, &A::processList, Qt::QueuedConnection);
      check2 = QObject::connect(&b, &B::updater , &a, &A::makeUpdate, Qt::QueuedConnection);
      std::cout<<"successful connection?: check1= "<<check1<<" and check2= "<<check2<<std::endl;
    
      //starting thread
      thread->start();
      
      //now: give an update from B to A:
      emit b.updater(42); // --> now it is working =)
      return app.exec();
    }
    
    

    corrected A.cxx (excerpt)

    ...
    
    void A::processList(){
      int i = 0;
      int q = 0;
      while(running){
        q++;
        if( i < list.size() and !list.isEmpty() ){
          //doing something that should not block main.cxx
          i++;
          if( i == list.size() ){
    	//end of list, reset list index
    	i = 0;
          }
        }
        if( !buffer.isEmpty() ){
          list.append(buffer);
          std::cout<<"Now list size is: "<<list.size()<<" and was added at step "<<q<<std::endl;
          buffer.clear();
          std::cout<<"after cleaning buffer, buffer size is:"<<buffer.size()<<std::endl;
        }
      }
    }
    ...
    

  • Lifetime Qt Champion

    Hi,

    One very important rule with signals and slots: do not emit signal from other classes. You shall only emit from within your class implementation.

    Please take a look at the QThread documentation to see an example of the worker object approach.


  • Lifetime Qt Champion

    @dan13l said in signals and slots across threads without QThread inheritance:

    So, what I learned is to explicitly set QueuedConnection together with the new syntax, because I tried again AutoConnection and then it fails to work.

    Then you're doing something wrong and your QObject('s) don't live in the appropriate thread.



  • @SGaist said in signals and slots across threads without QThread inheritance:

    One very important rule with signals and slots: do not emit signal from other classes. You shall only emit from within your class implementation.

    Hi @SGaist, thanks for that hint, I will try to implement it that way. Seems that for my real code it will be anyway necessary to create an additional object in which the emit can happen then.



  • @Christian-Ehrlicher said in signals and slots across threads without QThread inheritance:

    Then you're doing something wrong and your QObject('s) don't live in the appropriate thread.

    Hmm...okay, maybe I should investigate that further. Does it make a difference, when I send the QObject to the thread through

    obj.MoveToThread(thread);
    

    and when I create the connection? What I mean is, doing:

    obj.MoveToThread(thread);
    QObject::connect(...);
    

    or doing

    QObject::connect(...);
    
    obj.MoveToThread(thread);
    


  • @dan13l There is no difference if you first move the instance to another thread or connect signals/slots first.

    Have you read the documentation Signals & Slots?



  • @KroMignon okay, I just wanted to give some approach for @Christian-Ehrlicher`s comment that something is still not correct, when AutoConnect does not work. Now I see that my suggestion was indeed not the best one ... ๐Ÿ™ˆ


  • Moderators

    @dan13l said in signals and slots across threads without QThread inheritance:

    okay, I just wanted to give some approach for @Christian-Ehrlicher`s comment that something is still not correct

    What Christian means is that the connection should be agnostic to the object's thread and that if you force the connection types you can hide actual bugs (i.e. an object not in the correct thread) from yourself. The subtler point is that QObject::connect already defaults to the Qt::QueuedConnection iff your object is already in a different thread, while you can see in the stack that it was directly invoked if in fact you have a slot executed before the object's been moved. All in all: do not force the connection type to queued unless you actually need to call the slot in the same thread but later (i.e. through the event loop).



  • @dan13l It could be hard to understand at beginning how signals/slots works. But is a fundamental mechanism in Qt world.
    The connection type should never be specified (which means it is "automatic").
    In automatic mode, the connection mode will be decide at signal call:

    • when source and destination QObject are in same thread, direct connection will be used
    • when source and destination QObject are in different thread queued connection will be used

    By using queued connection, a shadow copy of each parameter will be done and used for the slot call. And the call will be registered in event queue from destination thread, executed when entering in the event queue. So it will be delayed.
    This mechanism can simplify multi-threading communication, but you have to be aware that a copy of each parameter will always be done!

    In direct connection, the call will be done directly, like a function call without shadow copy.

    Hope this will help you.



  • The first thing I see in the code is that there is not possibility for A to further process any events.

    If you do:

    QThread* thread = new QThread;
    ...
    thread->start();
    

    it will use the default implementation of QThread which will run an event loop.

    In between creating the thread and starting it you do:

    QObject::connect(thread, SIGNAL(started()), &a, SLOT(processList()), Qt::AutoConnection);
    

    which means that when the thread starts it will immediately start processing the first slot processList. Only after this slot finishes will the thread be able to process the next event from its event loop. However, you have a

    while(running)
    {
        ...
    }
    

    in you processList slot. So, I guess that your slot never finishes and QThread will never pick up any other events.

    One trick you can try is to put a repeating QTimer in your thread which has a timeout of 0ms and is connected to processList (assuming you remove the while(running)...). This will call processList over and over again when there is nothing else to do (kind of an infinite loop like you have now). But, between calls to processList other events can be handled, i.e. executing makeUpdate in your specific case.



  • Thank you again for your engagement regarding my questions! ๐Ÿ™‚

    @kshegunov ah, okay, so thank you for that explanation. I think now the example code is improved, because I need no specification for the Connection Type. (see below)

    @KroMignon also thanks to you for the deeper insight to to Connection Types. Yes, it helps me, also regarding some code efficiency considerations due to the creation of shallow copies.

    @SimonSchroeder Thanks a lot! First I did not think about further events, but in the end this is what I want to achieve. Therefore your 'trick' addresses directly my implementation problem and solves it. Fantastic!
    I hope I understood your idea correctly? What I did is the following:

    main.cxx

    #include <QApplication>
    #include <QThread>
    #include <iostream>
    #include "A.h"
    #include "B.h"
    int main(int argc , char *argv[]){
      QApplication app(argc, argv);
      //doing something for gui and user ...
      // ...
      std::cout<<"Hello Qt Forum."<<std::endl;
      // ...
      A a(1);
      B b(2);
      //creating thread 
      QThread* thread = new QThread;
      a.moveToThread(thread); // change thread affinity
      bool check1, check2;
      check1 = QObject::connect(thread, &QThread::started, &a, &A::timing);
      check2 = QObject::connect(&b, &B::updater , &a, &A::makeUpdate);
      std::cout<<"successful connection?: check1= "<<check1<<" and check2= "<<check2<<std::endl;
    
      //starting thread
      thread->start();
      
      //now: give an update from B to A:
      emit b.updater(42); // --> now it`s working
      for(int i = 0; i < 123; i++) std::cout<<"here in main with i = "<<i<<std::endl;
      emit b.updater(1001);// --> and again...
      return app.exec();
    }
    

    A.cxx (t is my timer defined in A.h with this=A as parent. Otherwise it will give me 'QObject::killTimer: Timers cannot be stopped from another thread' . I think it is due to different thread affinity.

    #include "A.h"
    #include <iostream>
    //
    A::A (int id){
      name = id;
      t->callOnTimeout(this, &A::processList);
    }
    
    void A::timing(){
      std::cout<<thread()<<std::endl;
        t->start();
      
    }
    
    void A::processList(){
      std::cout<<"in processList"<<std::endl;
      if( !buffer.isEmpty() ){
        list.append(buffer);
        std::cout<<"Now list size is: "<<list.size()<<std::endl;
        buffer.clear();
        std::cout<<"after cleaning buffer, buffer size is:"<<buffer.size()<<std::endl;
      }
      timing();
    }
    
    void A::makeUpdate( int update ){
      std::cout<<"Updating..."<<std::endl;
      std::cout<<thread()<<std::endl;
      buffer.append(update);
    }
    
    

    as my result, my console output gives me the following:

    Hello Qt Forum.
    successful connection?: check1= 1 and check2= 1
    here in main with i = 0
    here in main with i = 1
    here in main with i = 2
    here in main with i = 3
    ...
    0x16571e8
    ...
    Updating...
    0x16571e8
    ...
    here in main with i = 40
    here in main with i = 41
    ...
    in processList
    Now list size is: 1
    after cleaning buffer, buffer size is:0
    0x16571e8
    ...
    here in main with i = 100
    here in main with i = 101
    ...
    here in main with i = 122
    in processList
    0x16571e8
    Updating...
    0x16571e8
    in processList
    Now list size is: 2
    after cleaning buffer, buffer size is:0
    0x16571e8
    in processList
    ...
    

Log in to reply