Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Communication between multiple threads that run at different frequencies
Forum Updated to NodeBB v4.3 + New Features

Communication between multiple threads that run at different frequencies

Scheduled Pinned Locked Moved Solved General and Desktop
30 Posts 5 Posters 5.9k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • E Offline
    E Offline
    EvheMary
    wrote on last edited by EvheMary
    #9

    Thanks for all the replies, i created a dummy demo of what my application is (I combine some codes from stacks). Unfortunately, there are some errors such as:

    'QObject::~QObject: Timers cannot be stopped from another thread
    

    This happens when i tried to start a new worker after stopping the previous one, o when i closed the app
    Here is the code:

    import sys
    from PySide2.QtCore import *
    from PySide2.QtWidgets import *
    
    class Inlet_Worker(QObject):
    
        data = Signal(int)
    
        def __init__(self):
            super().__init__()
            self._stopped = False
            self._registered = False
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.routine)
            self.c = 0
    
        def starter(self):
            self.timer.start(1000)
    
        '''
        def run(self):
            count = 0
            self._stopped = False
            while not self._stopped:
                #if client.read_coils(address = 0x0802).bits[0]:
                count += 1
                if count % 20 == 0 and not self._registered:
                    self.updateBarcodeRegistration(True)
                    self.timer.start(2000)
                QCoreApplication.processEvents()
                QThread.msleep(100)
            self.updateBarcodeRegistration(False)
            self.timer.stop()
            print('Stopped')
        '''
    
        def routine(self):
            print(self.c)
            self.data.emit(self.c)
            self.c += 1
    
        def stop(self):
            self.timer.stop()
            print('stopped')
    
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
            self.dlg = Dialog()
            self.thread = QThread()
            self.button = QPushButton('Start')
            self.button2 = QPushButton('Stop')
            self.button3 = QPushButton('Window')
            layout = QHBoxLayout(self)
            layout.addWidget(self.button)
            layout.addWidget(self.button2)
            layout.addWidget(self.button3)
            self.button.clicked.connect(self.startThr)
            self.button2.clicked.connect(self.stopThr)
            self.button3.clicked.connect(self.showDlg)
    
        def showDlg(self):
            if not self.dlg.isVisible():
                self.dlg.show()
        
        def startThr(self):
            self.worker = Inlet_Worker()
            self.worker.moveToThread(self.thread)
            self.worker.data.connect(self.dlg.update)
            self.thread.started.connect(self.worker.starter)
            self.thread.start()
    
        def stopThr(self):
            self.worker.stop()
            self.thread.terminate()
            self.thread.wait()
    
        def closeEvent(self, event):
            self.worker.stop()
            self.thread.quit()
            self.thread.wait()
    
    class Dialog(QWidget):
        def __init__(self):
            super().__init__()
            self.text1 = QLabel("Label 1 : ")
            self.text2 = QLabel("Text2")
            layout = QHBoxLayout(self)
            layout.addWidget(self.text1)
            layout.addWidget(self.text2)
            self.setGeometry(400, 100, 100, 50)
    
        def update(self, sig):
            self.text2.setText(str(sig))
    
        def closeEvent(self, event):
            pass
    
    if __name__ == '__main__':
    
        app = QApplication(sys.argv)
        window = Window()
        window.setGeometry(750, 100, 200, 50)
        window.show()
        sys.exit(app.exec_())
    

    Can you explain why is this happening? Thanks in advance.

    1 Reply Last reply
    0
    • Axel SpoerlA Offline
      Axel SpoerlA Offline
      Axel Spoerl
      Moderators
      wrote on last edited by
      #10

      The timer is living in one thread and its stop() slot is called directly from another thread.
      Don't want the neighbor entering my flat without ringing the door bell, just to turn the music louder (or lower) - right?

      I am not a python guru, but something like
      QMetaObject.invokeMethod(self.timer, 'stop', Qt.QueuedConnection)
      should do what self.timer.stop()doesn't when the timer lives elswhere.
      QueuedConnectiondoesn't wait for the timer to be stopped. You can use other connection types if required.

      Software Engineer
      The Qt Company, Oslo

      1 Reply Last reply
      1
      • E Offline
        E Offline
        EvheMary
        wrote on last edited by
        #11

        Thanks a lot for the solution. It fixed the error. It seems I misunderstood what a Qtimer is. QTimer might also be a threading interface, hence why I can't just call its start and stop methods. I'm just still confused about why I need this method to stop the timer, but I can do it directly for the start method?
        And is QMetaObject works similarly with what a signal is?

        jsulmJ JonBJ 2 Replies Last reply
        0
        • E EvheMary

          Thanks a lot for the solution. It fixed the error. It seems I misunderstood what a Qtimer is. QTimer might also be a threading interface, hence why I can't just call its start and stop methods. I'm just still confused about why I need this method to stop the timer, but I can do it directly for the start method?
          And is QMetaObject works similarly with what a signal is?

          jsulmJ Offline
          jsulmJ Offline
          jsulm
          Lifetime Qt Champion
          wrote on last edited by
          #12

          @EvheMary said in Communication between multiple threads that run at different frequencies:

          QTimer might also be a threading interface

          What does this mean? QTimer is just a timer and has nothing to do with threads.
          "why I can't just call its start and stop" - because QTimer is not thread safe (only reentrant as documentation states), so you should not call its methods from other threads than the thread where the QTimer instance is living.

          https://forum.qt.io/topic/113070/qt-code-of-conduct

          1 Reply Last reply
          0
          • E EvheMary

            Thanks a lot for the solution. It fixed the error. It seems I misunderstood what a Qtimer is. QTimer might also be a threading interface, hence why I can't just call its start and stop methods. I'm just still confused about why I need this method to stop the timer, but I can do it directly for the start method?
            And is QMetaObject works similarly with what a signal is?

            JonBJ Offline
            JonBJ Offline
            JonB
            wrote on last edited by
            #13

            @EvheMary
            @Axel-Spoerl may well know more than I, but I have never had to use QMetaObject.invokeMethod(). Fine if that's good/safe/the right to do this/works for you. But my first thought would have been to send a signal from wherever to the thread which the thread has a slot on and does its own self.timer.stop() when received, if that is what you are trying to achieve. Am I wrong?

            1 Reply Last reply
            1
            • Axel SpoerlA Offline
              Axel SpoerlA Offline
              Axel Spoerl
              Moderators
              wrote on last edited by
              #14

              my first thought would have been to send a signal

              As usual, @JonB , your (first) thoughts are excellent!
              QMetaObject.invokeMethod() spares the definition of a signal, especially when you call a void slot without arguments, like QTimer::stop(). It can also be quite handy if you call it with the BlockingConnection argument, in which case you can block'n'wait for a return value :-)

              Software Engineer
              The Qt Company, Oslo

              JonBJ 1 Reply Last reply
              1
              • Axel SpoerlA Axel Spoerl

                my first thought would have been to send a signal

                As usual, @JonB , your (first) thoughts are excellent!
                QMetaObject.invokeMethod() spares the definition of a signal, especially when you call a void slot without arguments, like QTimer::stop(). It can also be quite handy if you call it with the BlockingConnection argument, in which case you can block'n'wait for a return value :-)

                JonBJ Offline
                JonBJ Offline
                JonB
                wrote on last edited by JonB
                #15

                @Axel-Spoerl
                Totally respect your answer. I have seen mentions of QMetaObject.invokeMethod() on the web, but I know it's used in "advanced" situations (including a lot from Python/PyQt/PySide, probably understandably) so haven't touched it! I can tell that from the 'stop' argument being a literal string it's going to look up the method to call by name ("reflection"), like the old SIGNAL/SLOT() macros approach did; so being more a C++ purist I would tend to avoid that, preferring compile-time safety/argument checking etc.

                Axel SpoerlA 1 Reply Last reply
                1
                • JonBJ JonB

                  @Axel-Spoerl
                  Totally respect your answer. I have seen mentions of QMetaObject.invokeMethod() on the web, but I know it's used in "advanced" situations (including a lot from Python/PyQt/PySide, probably understandably) so haven't touched it! I can tell that from the 'stop' argument being a literal string it's going to look up the method to call by name ("reflection"), like the old SIGNAL/SLOT() macros approach did; so being more a C++ purist I would tend to avoid that, preferring compile-time safety/argument checking etc.

                  Axel SpoerlA Offline
                  Axel SpoerlA Offline
                  Axel Spoerl
                  Moderators
                  wrote on last edited by
                  #16

                  @JonB
                  Totally fair point! A kitten dies, each time you resolve a symbol with a string search at run time....in Python.
                  There are more invokeMethod() overloads for the C++ lovers from 6.4 onward.

                  Software Engineer
                  The Qt Company, Oslo

                  JonBJ 1 Reply Last reply
                  1
                  • Axel SpoerlA Axel Spoerl

                    @JonB
                    Totally fair point! A kitten dies, each time you resolve a symbol with a string search at run time....in Python.
                    There are more invokeMethod() overloads for the C++ lovers from 6.4 onward.

                    JonBJ Offline
                    JonBJ Offline
                    JonB
                    wrote on last edited by
                    #17

                    @Axel-Spoerl said in Communication between multiple threads that run at different frequencies:

                    A kitten dies, each time you resolve a symbol with a string search at run time....in Python.

                    :)

                    1 Reply Last reply
                    0
                    • E Offline
                      E Offline
                      EvheMary
                      wrote on last edited by
                      #18

                      Thanks for all these comments. I might not fully understand since I'm still a beginner in qt, so sorry if I mentioned something wrong. I thought that since I use a signal to communicate with the Qtimer thread it should be safe. And I should be calling the stop method from the thread that it was created. Is my implementation of the signals and slot not correct?
                      my understanding of using the signal and slot is :
                      UI Thread --> send signal --> stop method (slot) at another thread --> QTimer stop

                      JonBJ 1 Reply Last reply
                      0
                      • E EvheMary

                        Thanks for all these comments. I might not fully understand since I'm still a beginner in qt, so sorry if I mentioned something wrong. I thought that since I use a signal to communicate with the Qtimer thread it should be safe. And I should be calling the stop method from the thread that it was created. Is my implementation of the signals and slot not correct?
                        my understanding of using the signal and slot is :
                        UI Thread --> send signal --> stop method (slot) at another thread --> QTimer stop

                        JonBJ Offline
                        JonBJ Offline
                        JonB
                        wrote on last edited by
                        #19

                        @EvheMary
                        You call self.worker.stop() (which calls self.timer.stop()) directly from the UI thread.

                        1 Reply Last reply
                        1
                        • E Offline
                          E Offline
                          EvheMary
                          wrote on last edited by EvheMary
                          #20

                          @JonB But if i call self.worker.stop() that calls self.timer.stop() from the UI Thread, the error QObject::~QObject: Timers cannot be stopped from another thread appears and that's what I want to know what causes it and how to remove it. If i change self.timer.stop() with QMetaObject.invokeMethod(self.timer, 'stop', Qt.AutoConnection) the error don't appear.

                          jsulmJ JonBJ 2 Replies Last reply
                          0
                          • E EvheMary

                            @JonB But if i call self.worker.stop() that calls self.timer.stop() from the UI Thread, the error QObject::~QObject: Timers cannot be stopped from another thread appears and that's what I want to know what causes it and how to remove it. If i change self.timer.stop() with QMetaObject.invokeMethod(self.timer, 'stop', Qt.AutoConnection) the error don't appear.

                            jsulmJ Offline
                            jsulmJ Offline
                            jsulm
                            Lifetime Qt Champion
                            wrote on last edited by
                            #21

                            @EvheMary said in Communication between multiple threads that run at different frequencies:

                            that's what I want to know what causes it

                            Your worker object lives in another thread. So, if you call self.worker.stop() in UI thread stop() will be executed in UI thread and it will also call self.timer.stop() in UI thread. But self.timer also lives in the worker thread, so you get that warning (QTimer is not thread safe).
                            To avoid this you should NOT call self.worker.stop() directly from the UI thread but instead either use invokeMethod() or connect self.worker.stop() to a signal in your UI and emit this signal to stop the worker.

                            https://forum.qt.io/topic/113070/qt-code-of-conduct

                            1 Reply Last reply
                            1
                            • E Offline
                              E Offline
                              EvheMary
                              wrote on last edited by
                              #22

                              @jsulm Yes. Thanks for the answer. Now I know that calling method in another thread from the UI thread is not good. But I don't know why when I connect a signal from UI Thread to the self.worker.stop(), the error persists. It will only be gone when I use invokeMethod() instead of self.timer.stop(), which leads to why I suspect QTimer to be another threading interface (because I need to send another signal to the timer itself).

                              jsulmJ 1 Reply Last reply
                              0
                              • E EvheMary

                                @jsulm Yes. Thanks for the answer. Now I know that calling method in another thread from the UI thread is not good. But I don't know why when I connect a signal from UI Thread to the self.worker.stop(), the error persists. It will only be gone when I use invokeMethod() instead of self.timer.stop(), which leads to why I suspect QTimer to be another threading interface (because I need to send another signal to the timer itself).

                                jsulmJ Offline
                                jsulmJ Offline
                                jsulm
                                Lifetime Qt Champion
                                wrote on last edited by
                                #23

                                @EvheMary said in Communication between multiple threads that run at different frequencies:

                                But I don't know why when I connect a signal from UI Thread to the self.worker.stop()

                                Can you show how you did the connection?

                                https://forum.qt.io/topic/113070/qt-code-of-conduct

                                1 Reply Last reply
                                0
                                • Axel SpoerlA Offline
                                  Axel SpoerlA Offline
                                  Axel Spoerl
                                  Moderators
                                  wrote on last edited by
                                  #24

                                  Adding to @jsulm :

                                  what causes it

                                  Theoretically, two different threads could call the QTimer's stop()method at the same time. Or, even worse, one of them calls start().
                                  The warning is triggered, if a slot is called from a thread different from the timer's living environment.
                                  Emitting a signal or calling invokeMethod() makes sure that concurrent calls are properly serialized, posted into the timer's event loop and executed within the timer's thread. Such behaviour is not QTimerspecific. It applies to most Qt classes, check this for more information.

                                  Software Engineer
                                  The Qt Company, Oslo

                                  1 Reply Last reply
                                  1
                                  • E EvheMary

                                    @JonB But if i call self.worker.stop() that calls self.timer.stop() from the UI Thread, the error QObject::~QObject: Timers cannot be stopped from another thread appears and that's what I want to know what causes it and how to remove it. If i change self.timer.stop() with QMetaObject.invokeMethod(self.timer, 'stop', Qt.AutoConnection) the error don't appear.

                                    JonBJ Offline
                                    JonBJ Offline
                                    JonB
                                    wrote on last edited by
                                    #25

                                    @EvheMary said in Communication between multiple threads that run at different frequencies:

                                    @JonB But if i call self.worker.stop() that calls self.timer.stop() from the UI Thread, the error QObject::~QObject: Timers cannot be stopped from another thread appears and that's what I want to know what causes it and how to remove it.

                                    That is precisely what I explained: you call self.timer.stop() from the UI Thread, that is not the thread where the timer lives, hence the explicit error message telling you what is wrong.

                                    But I don't know why when I connect a signal from UI Thread to the self.worker.stop(), the error persists.

                                    As @jsulm said, show your connect() statement for this.

                                    1 Reply Last reply
                                    0
                                    • E Offline
                                      E Offline
                                      EvheMary
                                      wrote on last edited by EvheMary
                                      #26

                                      I actually updated my code since i also asks similar question in stacks overflow (though still without solution). I'll show the important parts only:

                                      class Window(QWidget):
                                      
                                          startSig = Signal()
                                          stopSig = Signal()
                                      
                                          def __init__(self):
                                                      self.button.clicked.connect(self.startThr)
                                                      self.button2.clicked.connect(self.stopThr)    # I called the stop method (for the UI Thread) here
                                                      self.button3.clicked.connect(self.showDlg)
                                      
                                          def startThr(self):
                                                      if self.thread is None or not self.thread.isRunning():
                                                      self.thread = QThread()
                                                      self.worker = Inlet_Worker()
                                                      self.worker.moveToThread(self.thread)
                                                      self.worker.data.connect(self.dlg.update)
                                                      self.worker.tabs.connect(self.switch_tab)
                                                      self.stopSig.connect(self.worker.stop)    # I connect the signal here
                                                      self.worker.finish.connect(self.finished)
                                                      self.thread.started.connect(self.worker.starter)
                                                      self.thread.start()
                                      
                                          def stopThr(self):
                                                  self.stopSig.emit()    # Emit stop signal
                                      

                                      And here is the thread

                                      class Inlet_Worker(QObject):
                                      
                                          data = Signal(int)
                                          tabs = Signal(int)
                                          finish = Signal()
                                      
                                          def __init__(self):
                                              super().__init__()
                                              self._stopped = False
                                              self._registered = False
                                              self.init_timers()
                                              self.c = 0
                                              self.d = 0
                                      
                                          def init_timers(self):
                                              self.timer1 = QTimer(self)
                                              self.timer1.timeout.connect(self.routine)
                                              self.timer2 = QTimer(self)
                                              self.timer2.timeout.connect(self.routine2)
                                      
                                          def starter(self):
                                              self.timer1.start(1000)
                                              self.timer2.start(2000)
                                      
                                          def routine(self):
                                              self.data.emit(self.c)
                                              self.c += 1
                                              
                                          def routine2(self):
                                              self.tabs.emit(self.d)
                                              self.d += 1
                                      
                                          @Slot()
                                          def stop(self):
                                              self.timer1.stop()
                                              self.timer2.stop()
                                              print('stopped')
                                              self.finish.emit()
                                      

                                      The stop method in UI Thread emit signal
                                      I connect the signal to the self.timer.stop() in the thread
                                      I can upload the full code if necessary.

                                      jsulmJ 1 Reply Last reply
                                      0
                                      • E EvheMary

                                        I actually updated my code since i also asks similar question in stacks overflow (though still without solution). I'll show the important parts only:

                                        class Window(QWidget):
                                        
                                            startSig = Signal()
                                            stopSig = Signal()
                                        
                                            def __init__(self):
                                                        self.button.clicked.connect(self.startThr)
                                                        self.button2.clicked.connect(self.stopThr)    # I called the stop method (for the UI Thread) here
                                                        self.button3.clicked.connect(self.showDlg)
                                        
                                            def startThr(self):
                                                        if self.thread is None or not self.thread.isRunning():
                                                        self.thread = QThread()
                                                        self.worker = Inlet_Worker()
                                                        self.worker.moveToThread(self.thread)
                                                        self.worker.data.connect(self.dlg.update)
                                                        self.worker.tabs.connect(self.switch_tab)
                                                        self.stopSig.connect(self.worker.stop)    # I connect the signal here
                                                        self.worker.finish.connect(self.finished)
                                                        self.thread.started.connect(self.worker.starter)
                                                        self.thread.start()
                                        
                                            def stopThr(self):
                                                    self.stopSig.emit()    # Emit stop signal
                                        

                                        And here is the thread

                                        class Inlet_Worker(QObject):
                                        
                                            data = Signal(int)
                                            tabs = Signal(int)
                                            finish = Signal()
                                        
                                            def __init__(self):
                                                super().__init__()
                                                self._stopped = False
                                                self._registered = False
                                                self.init_timers()
                                                self.c = 0
                                                self.d = 0
                                        
                                            def init_timers(self):
                                                self.timer1 = QTimer(self)
                                                self.timer1.timeout.connect(self.routine)
                                                self.timer2 = QTimer(self)
                                                self.timer2.timeout.connect(self.routine2)
                                        
                                            def starter(self):
                                                self.timer1.start(1000)
                                                self.timer2.start(2000)
                                        
                                            def routine(self):
                                                self.data.emit(self.c)
                                                self.c += 1
                                                
                                            def routine2(self):
                                                self.tabs.emit(self.d)
                                                self.d += 1
                                        
                                            @Slot()
                                            def stop(self):
                                                self.timer1.stop()
                                                self.timer2.stop()
                                                print('stopped')
                                                self.finish.emit()
                                        

                                        The stop method in UI Thread emit signal
                                        I connect the signal to the self.timer.stop() in the thread
                                        I can upload the full code if necessary.

                                        jsulmJ Offline
                                        jsulmJ Offline
                                        jsulm
                                        Lifetime Qt Champion
                                        wrote on last edited by
                                        #27

                                        @EvheMary said in Communication between multiple threads that run at different frequencies:

                                        self.stopSig.connect(self.worker.stop)

                                        Maybe you need explicetly define Qt::QueuedConnection (in C++ it is default for connections across threads).

                                        https://forum.qt.io/topic/113070/qt-code-of-conduct

                                        E 1 Reply Last reply
                                        1
                                        • jsulmJ jsulm

                                          @EvheMary said in Communication between multiple threads that run at different frequencies:

                                          self.stopSig.connect(self.worker.stop)

                                          Maybe you need explicetly define Qt::QueuedConnection (in C++ it is default for connections across threads).

                                          E Offline
                                          E Offline
                                          EvheMary
                                          wrote on last edited by
                                          #28

                                          @jsulm Oh wow, this also works. Unfortunately, I have already marked the previous answer as the solution but this works too. It seems that the default type for connect in pyqt is Qt.AutoConnection. Do you mind explaining why this happens when using AutoConnection while it works with queuedConnection?

                                          jsulmJ 1 Reply Last reply
                                          0

                                          • Login

                                          • Login or register to search.
                                          • First post
                                            Last post
                                          0
                                          • Categories
                                          • Recent
                                          • Tags
                                          • Popular
                                          • Users
                                          • Groups
                                          • Search
                                          • Get Qt Extensions
                                          • Unsolved