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 893 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, 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 Online
      P Online
      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 Online
                    P Online
                    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

                        12/13

                        24 Jan 2025, 00:10

                        • Login

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