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
Forum Updated to NodeBB v4.3 + New Features

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

Scheduled Pinned Locked Moved Solved Qt for Python
13 Posts 4 Posters 1.2k Views 3 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.
  • F Offline
    F Offline
    friedemannkleint
    wrote on 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())
    
    Pl45m4P 1 Reply Last reply
    2
    • T Offline
      T Offline
      thinkinmachine
      wrote on 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

        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())
        
        Pl45m4P Offline
        Pl45m4P Offline
        Pl45m4
        wrote on 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
        2
        • Pl45m4P Pl45m4

          @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 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 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 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
              1
              • T thinkinmachine has marked this topic as solved on
              • F friedemannkleint

                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 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
                1
                • T thinkinmachine

                  @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 last edited by FlowOverNestedThreads
                  #9

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

                  T 1 Reply Last reply
                  0
                  • T Offline
                    T Offline
                    thinkinmachine
                    wrote on 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

                      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.

                      Pl45m4P Offline
                      Pl45m4P Offline
                      Pl45m4
                      wrote on 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
                      1
                      • F FlowOverNestedThreads

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

                        T Offline
                        T Offline
                        thinkinmachine
                        wrote on 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
                        • Pl45m4P Pl45m4

                          @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 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

                          • Login

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