Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. [PySide6] Slot Executed in Signal's Thread Even With Auto & QueuedConnection

[PySide6] Slot Executed in Signal's Thread Even With Auto & QueuedConnection

Scheduled Pinned Locked Moved Solved Qt for Python
13 Posts 4 Posters 892 Views
  • 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.
  • T Offline
    T Offline
    thinkinmachine
    wrote on 21 Jan 2025, 06:16 last edited by thinkinmachine
    #1

    I'm having trouble with signal & slot in different threads. The slot is always executed in the signal caller's thread, even with explicit QueuedConnection . Below is the minimal example.

    import sys
    
    from PySide6.QtCore import QObject, Signal, QThread, Qt
    from PySide6.QtWidgets import QMainWindow, QApplication
    
    
    class Worker(QObject):
        update = Signal()
    
        def run(self):
            print(f"worker: {QThread.currentThread().objectName()}")
            self.update.emit()
    
    
    class Window(QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            worker = Worker()
            thread = QThread()
            thread.setObjectName('test thread')
            worker.moveToThread(thread)
            thread.started.connect(worker.run)
            worker.update.connect(self.update)
            thread.start()
    
        def update(self):
            print(f"window: {QThread.currentThread().objectName()}")
    
    
    if __name__ == '__main__':
        app = QApplication()
        w = Window()
        w.show()
        sys.exit(app.exec())
    

    The output of this minimal example is

    worker: Qt mainThread
    window: test thread
    

    Seems like the slot is always executed in signal caller's thread, because the thread of thread.start is main thread, and the thread of worker.update is test thread. The result is the same after I add QueuedConnection explicitly.

    I've read the document of QThread several times, including this one, and it seems like I'm using threads in the correct way. Please correct me if I'm wrong.

    My PySide version is 6.8.1. Any help would be greatly appreciated.

    P 1 Reply Last reply 23 Jan 2025, 12:13
    0
    • S SGaist moved this topic from General and Desktop on 21 Jan 2025, 13:03
    • F Offline
      F Offline
      friedemannkleint
      wrote on 21 Jan 2025, 15:40 last edited by friedemannkleint
      #2

      The correct way to implement threads is to overwrite QThread.run() to do the work. We also recommend using the @Slot decorator. Consider:

      import sys
      
      from PySide6.QtCore import QObject, Signal, QThread, Qt, Slot
      from PySide6.QtWidgets import QMainWindow, QApplication
      
      
      class Worker(QThread):
          update = Signal()
      
          def run(self):
              print(f"worker: {QThread.currentThread().objectName()}")
              self.update.emit()
      
      
      class Window(QMainWindow):
          def __init__(self, parent=None):
              super().__init__(parent)
              self._worker = Worker()
              self._worker.setObjectName('test thread')
              self._worker.update.connect(self.update)
              self._worker.start()
      
          def closeEvent(self, e):
              self._worker.wait()
              e.accept()
      
          @Slot()
          def update(self):
              print(f"window: {QThread.currentThread().objectName()}")
      
      
      if __name__ == '__main__':
          app = QApplication()
          w = Window()
          w.show()
          sys.exit(app.exec())
      
      P 1 Reply Last reply 21 Jan 2025, 19:02
      2
      • T Offline
        T Offline
        thinkinmachine
        wrote on 21 Jan 2025, 18:43 last edited by
        #3

        Hi @friedemannkleint ,

        Thanks for the reply. Sorry I forgot to add Slot decorator in the example. The result is still the same with the decorator.

        import sys
        
        from PySide6.QtCore import QObject, Signal, QThread, Qt, Slot
        from PySide6.QtWidgets import QMainWindow, QApplication
        
        
        class Worker(QObject):
            update = Signal()
        
            def run(self):
                print(f"worker: {QThread.currentThread().objectName()}")
                self.update.emit()
        
        
        class Window(QMainWindow):
            def __init__(self, parent=None):
                super().__init__(parent)
                worker = Worker()
                thread = QThread(self)
                thread.setObjectName('test thread')
                worker.moveToThread(thread)
                thread.started.connect(worker.run)
                worker.update.connect(self.update)
                thread.start()
        
            @Slot()
            def update(self):
                print(f"window: {QThread.currentThread().objectName()}")
        
        
        if __name__ == '__main__':
            app = QApplication()
            w = Window()
            w.show()
            sys.exit(app.exec())
        

        Subclassing QThread did yield correct result.

        import sys
        
        from PySide6.QtCore import QObject, Signal, QThread, Qt, Slot
        from PySide6.QtWidgets import QMainWindow, QApplication
        
        
        class Thread(QThread):
            update = Signal()
        
            def run(self):
                print(f"thread: {QThread.currentThread().objectName()}")
                self.update.emit()
        
        
        class Window(QMainWindow):
            def __init__(self, parent=None):
                super().__init__(parent)
                thread = Thread(self)
                thread.setObjectName('test thread')
                thread.update.connect(self.update)
                thread.start()
        
            @Slot()
            def update(self):
                print(f"window: {QThread.currentThread().objectName()}")
        
        
        if __name__ == '__main__':
            app = QApplication()
            w = Window()
            w.show()
            sys.exit(app.exec())
        

        However, I remember it's suggested to use a separate worker in some posts I read. Is it a PySide bug that the worker method doesn't work as expected? It's almost identical to the C++ example in the official document.

        1 Reply Last reply
        0
        • F friedemannkleint
          21 Jan 2025, 15:40

          The correct way to implement threads is to overwrite QThread.run() to do the work. We also recommend using the @Slot decorator. Consider:

          import sys
          
          from PySide6.QtCore import QObject, Signal, QThread, Qt, Slot
          from PySide6.QtWidgets import QMainWindow, QApplication
          
          
          class Worker(QThread):
              update = Signal()
          
              def run(self):
                  print(f"worker: {QThread.currentThread().objectName()}")
                  self.update.emit()
          
          
          class Window(QMainWindow):
              def __init__(self, parent=None):
                  super().__init__(parent)
                  self._worker = Worker()
                  self._worker.setObjectName('test thread')
                  self._worker.update.connect(self.update)
                  self._worker.start()
          
              def closeEvent(self, e):
                  self._worker.wait()
                  e.accept()
          
              @Slot()
              def update(self):
                  print(f"window: {QThread.currentThread().objectName()}")
          
          
          if __name__ == '__main__':
              app = QApplication()
              w = Window()
              w.show()
              sys.exit(app.exec())
          
          P Offline
          P Offline
          Pl45m4
          wrote on 21 Jan 2025, 19:02 last edited by
          #4

          @friedemannkleint said in [PySide6] Slot Executed in Signal's Thread Even With Auto & QueuedConnection:

          The correct way to implement threads is to overwrite QThread.run() to do the work.

          Why is there only one correct approach?!
          Shouldn't the worker thread approach work the same (as also when using C++)?

          Here in this topic the worker approach seems to function correctly (besides the mistakes OP made there).


          If debugging is the process of removing software bugs, then programming must be the process of putting them in.

          ~E. W. Dijkstra

          T 1 Reply Last reply 21 Jan 2025, 20:17
          2
          • P Pl45m4
            21 Jan 2025, 19:02

            @friedemannkleint said in [PySide6] Slot Executed in Signal's Thread Even With Auto & QueuedConnection:

            The correct way to implement threads is to overwrite QThread.run() to do the work.

            Why is there only one correct approach?!
            Shouldn't the worker thread approach work the same (as also when using C++)?

            Here in this topic the worker approach seems to function correctly (besides the mistakes OP made there).

            T Offline
            T Offline
            thinkinmachine
            wrote on 21 Jan 2025, 20:17 last edited by
            #5

            @Pl45m4

            Thanks for the reply! I did read that post. I’m really confused to see that one works because it’s almost the same as mine. Do you have any idea why this is happening?

            1 Reply Last reply
            0
            • T Offline
              T Offline
              thinkinmachine
              wrote on 21 Jan 2025, 20:55 last edited by thinkinmachine
              #6

              It's reported to Qt Bug Tracker: https://bugreports.qt.io/browse/PYSIDE-2990

              Update: tested cpp equivalent version and it worked fine. It should be a PySide issue.

              1 Reply Last reply
              2
              • F Offline
                F Offline
                friedemannkleint
                wrote on 22 Jan 2025, 07:24 last edited by friedemannkleint
                #7

                Please don't use that technique of connecting a worker to the QThread.start() signal. It leaks a thread hanging in its exec() function since that is called by the default implementation of run(), plus the signal execution is blocked by the work.

                T 1 Reply Last reply 22 Jan 2025, 16:48
                1
                • T thinkinmachine has marked this topic as solved on 22 Jan 2025, 16:45
                • F friedemannkleint
                  22 Jan 2025, 07:24

                  Please don't use that technique of connecting a worker to the QThread.start() signal. It leaks a thread hanging in its exec() function since that is called by the default implementation of run(), plus the signal execution is blocked by the work.

                  T Offline
                  T Offline
                  thinkinmachine
                  wrote on 22 Jan 2025, 16:48 last edited by thinkinmachine
                  #8

                  @friedemannkleint
                  Thank you so much. I didn't notice update would be a issue.

                  However I found another issue. When I use PyCharm's debugger to run the code the thread of worker becomes main thread again. It might have to do with debugger settings.

                  Update: When the main thread is blocked by breakpoint, the child threads are all blocked as well. This ONLY happens with PyCharm debugging.

                  F 1 Reply Last reply 22 Jan 2025, 23:35
                  1
                  • T thinkinmachine
                    22 Jan 2025, 16:48

                    @friedemannkleint
                    Thank you so much. I didn't notice update would be a issue.

                    However I found another issue. When I use PyCharm's debugger to run the code the thread of worker becomes main thread again. It might have to do with debugger settings.

                    Update: When the main thread is blocked by breakpoint, the child threads are all blocked as well. This ONLY happens with PyCharm debugging.

                    F Offline
                    F Offline
                    FlowOverNestedThreads
                    wrote on 22 Jan 2025, 23:35 last edited by FlowOverNestedThreads
                    #9

                    @thinkinmachine can confirm (dear lord, how have you even pinpointed the reason?), also related

                    T 1 Reply Last reply 24 Jan 2025, 00:10
                    0
                    • T Offline
                      T Offline
                      thinkinmachine
                      wrote on 23 Jan 2025, 06:13 last edited by thinkinmachine
                      #10

                      Also, it doesn't work if lambda is used. Probably because Qt doesn't know which thread the lambda function should be in.

                      self._worker.update.connect(lambda: self.slotUpdate())
                      
                      1 Reply Last reply
                      0
                      • T thinkinmachine
                        21 Jan 2025, 06:16

                        I'm having trouble with signal & slot in different threads. The slot is always executed in the signal caller's thread, even with explicit QueuedConnection . Below is the minimal example.

                        import sys
                        
                        from PySide6.QtCore import QObject, Signal, QThread, Qt
                        from PySide6.QtWidgets import QMainWindow, QApplication
                        
                        
                        class Worker(QObject):
                            update = Signal()
                        
                            def run(self):
                                print(f"worker: {QThread.currentThread().objectName()}")
                                self.update.emit()
                        
                        
                        class Window(QMainWindow):
                            def __init__(self, parent=None):
                                super().__init__(parent)
                                worker = Worker()
                                thread = QThread()
                                thread.setObjectName('test thread')
                                worker.moveToThread(thread)
                                thread.started.connect(worker.run)
                                worker.update.connect(self.update)
                                thread.start()
                        
                            def update(self):
                                print(f"window: {QThread.currentThread().objectName()}")
                        
                        
                        if __name__ == '__main__':
                            app = QApplication()
                            w = Window()
                            w.show()
                            sys.exit(app.exec())
                        

                        The output of this minimal example is

                        worker: Qt mainThread
                        window: test thread
                        

                        Seems like the slot is always executed in signal caller's thread, because the thread of thread.start is main thread, and the thread of worker.update is test thread. The result is the same after I add QueuedConnection explicitly.

                        I've read the document of QThread several times, including this one, and it seems like I'm using threads in the correct way. Please correct me if I'm wrong.

                        My PySide version is 6.8.1. Any help would be greatly appreciated.

                        P Offline
                        P Offline
                        Pl45m4
                        wrote on 23 Jan 2025, 12:13 last edited by
                        #11

                        @thinkinmachine said in [PySide6] Slot Executed in Signal's Thread Even With Auto & QueuedConnection:

                        Probably because Qt doesn't know

                        It's not a Qt thing I would say, rather than Python-related.
                        I'm not the best at Python, but AFAIK there is not "context" in Python lambdas (correct me if I'm wrong).
                        In C++ the Qt Signal-Slot lambda is executed in the "context" (if any) object's thread, or the receiving one.
                        Like

                        // ------------------------------------
                        Worker w;
                        int result = 42;
                        emit w.workFinished(result);
                        // ------------------------------------
                        connect(&w, &Worker::workFinished, this, [=](int res) {
                               int result = res + 123;
                               doSomethingWith(result);
                        });
                        
                        // ------------------------------------
                        

                        If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                        ~E. W. Dijkstra

                        T 1 Reply Last reply 24 Jan 2025, 00:11
                        1
                        • F FlowOverNestedThreads
                          22 Jan 2025, 23:35

                          @thinkinmachine can confirm (dear lord, how have you even pinpointed the reason?), also related

                          T Offline
                          T Offline
                          thinkinmachine
                          wrote on 24 Jan 2025, 00:10 last edited by
                          #12

                          @FlowOverNestedThreads

                          I reported this bug to JetBrain PyCharm team (here). Hopefully they can help figure out what's going on. Thanks for providing stackoverflow link.

                          1 Reply Last reply
                          0
                          • P Pl45m4
                            23 Jan 2025, 12:13

                            @thinkinmachine said in [PySide6] Slot Executed in Signal's Thread Even With Auto & QueuedConnection:

                            Probably because Qt doesn't know

                            It's not a Qt thing I would say, rather than Python-related.
                            I'm not the best at Python, but AFAIK there is not "context" in Python lambdas (correct me if I'm wrong).
                            In C++ the Qt Signal-Slot lambda is executed in the "context" (if any) object's thread, or the receiving one.
                            Like

                            // ------------------------------------
                            Worker w;
                            int result = 42;
                            emit w.workFinished(result);
                            // ------------------------------------
                            connect(&w, &Worker::workFinished, this, [=](int res) {
                                   int result = res + 123;
                                   doSomethingWith(result);
                            });
                            
                            // ------------------------------------
                            
                            T Offline
                            T Offline
                            thinkinmachine
                            wrote on 24 Jan 2025, 00:11 last edited by
                            #13

                            @Pl45m4

                            Thanks for your explanation! I'll keep you posted if I find out something new on this topic.

                            1 Reply Last reply
                            0

                            5/13

                            21 Jan 2025, 20:17

                            topic:navigator.unread, 8
                            • Login

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