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

Explicitly running a procedure in a different QThread



  • Hi. I am stuck with the problem of explicitly executing something (in the end creating certain GUI elements) in the main thread from a QObject that runs in its own thread. I don't know much about threading, but I know that the communication must be set up using Qt's signals and slots. However, the signals seem to get "lost", the following code reproduces this, the do_something method isn't triggered at all. The goal is to reach 'success' after ThreadBridge.do_something() has finished executing.

    import sys
    from PySide2.QtWidgets import QMainWindow, QApplication
    from PySide2.QtCore import QObject, Signal, QThread
    
    
    class ThreadBridge(QObject):
    
        done = Signal()
    
        def do_something(self):
            print('doing something!')
            self.done.emit()
    
    
    class Controller(QObject):
    
        some_request = Signal()
    
        def create(self, main_thread):
            bridge = ThreadBridge()
            bridge.moveToThread(main_thread)
    
            self.some_request.connect(bridge.do_something)
            bridge.done.connect(self.finish)
    
            self.some_request.emit()  # nothing happens
    
        def finish(self):
            print('success')
    
    
    class MainWindow(QMainWindow):
        init_controller_signal = Signal(object)
    
        def __init__(self):
            super().__init__()
    
            self.controller_thread = QThread()
            self.controller_thread.start()
    
            self.controller = Controller()
            self.init_controller_signal.connect(self.controller.create)
            self.controller.moveToThread(self.controller_thread)
            self.init_controller_signal.emit(self.thread())
    
    
    if __name__ == "__main__":
        app = QApplication()
        mw = MainWindow()
        mw.show()
        sys.exit(app.exec_())
    
    

    Help appreciated!


  • Lifetime Qt Champion

    @nutrx said in Explicitly running a procedure in a different QThread:

    def create(self, main_thread):
    bridge = ThreadBridge()

    bridge is a local variable and is destroyed as soon as create() method terminates...


  • Lifetime Qt Champion

    Hi,

    bridge is only valid for the lifetime of the create method so it's likely destroyed before it can even start to do something.



  • oh of course, classic I guess, thank you very much, I was actually stuck on that. One more thing that's closely related to this: in the following code I passed the controller itself, so the bridge can set an attribute after completing computation. Assuming I only have these two threads, would this be considered bad design?

    import sys
    import time
    import random
    
    from PySide2.QtWidgets import QMainWindow, QApplication
    from PySide2.QtCore import QObject, Signal, QThread
    
    
    class ThreadBridge(QObject):
    
        def do_something(self, obj):
            obj.tmp_data_ref = random.random()
    
    
    class Controller(QObject):
    
        some_request = Signal(object)
    
        def __init__(self, main_thread):
            super().__init__()
            self.tmp_data_ref = None
            self.bridge = ThreadBridge()
            self.bridge.moveToThread(main_thread)
    
        def create(self):
            self.tmp_data_ref = None
    
            self.some_request.connect(self.bridge.do_something)
            self.some_request.emit(self)
    
            while self.tmp_data_ref is None:
                time.sleep(0.001)
    
            data = self.tmp_data_ref
            print('success!', data)
    
    
    class MainWindow(QMainWindow):
        init_controller_signal = Signal()
    
        def __init__(self):
            super().__init__()
    
            self.controller_thread = QThread()
            self.controller_thread.start()
    
            self.controller = Controller(self.thread())
            self.init_controller_signal.connect(self.controller.create)
            self.controller.moveToThread(self.controller_thread)
            self.init_controller_signal.emit()
    
    
    if __name__ == "__main__":
        app = QApplication()
        mw = MainWindow()
        mw.show()
        sys.exit(app.exec_())
    

  • Lifetime Qt Champion

    Yes it is for several reasons:

    1. you modify a GUI object from a different thread which is something you should not do.
    2. you create a useless tight coupling between your worker object and your MainWindow. The worker object does not need to know anything about the use of the data it might generate.

    Clean separation of responsibilities allows you to more easily maintain your software.



  • @SGaist

    1. where do I modify GUI? I only modify the controller, no?
    2. I think I don't really understand. Why do I have mixed resposibilities there? In my actual application I am trying to use this to make an object (like the controller) in the separate thread wait until the initialization of a GUI element that it triggered finished in the main thread, to then start a computation.

  • Lifetime Qt Champion

    1. My bad ! The code scrolling made me misread what object you were modifying from your thread.
    2. Following your explanation of your goal, the idea of the separation of responsibility would be that your GUI should use a signal to let whoever is interested in that information know that it is ready. You would connect your controller to that signal so it can trigger the work you want to do.


  • @SGaist Ah yes, that's how I implemented it most of the time. The reason I need something like this a few times is that it happens in an API method that is supposed to return data that can only be computed (by the controller in this case) after the corresponding GUI element has been initialized in the main thread (or, in the example above, after the ThreadBridge computed some data in the main thread). Since this is probably not an uncommon issue, is there a better solution to this?
    It's not that the controller depends on the GUI! It's the other way around, but I need to make sure the controller waits until the GUI is ready.


  • Lifetime Qt Champion

    You can initialize everything and then you can use a single shot 0 delay QTimer to trigger the start of your controller. This will be done a soon as the event loop start in that case.



  • @SGaist apart from waiting for the thread's event loop to start, what would be the difference to just using signals and slots? This wouldn't solve the issue of waiting for initialized GUI to then (in the same method) start a computation whose result can then be returned, I guess


  • Lifetime Qt Champion

    Do you mean the difference between the zero delay QTimer and using signals and slots ?



  • @SGaist yes, at least in case I have this requirement of waiting for the GUI to be fully initialized while the API method is executed, to then safely start the computation and return the results


  • Lifetime Qt Champion

    Oh I see, I did not get the while part.

    Just to sum up a bit so we are on the same line:

    • you have API calls that are done while also building your GUI
    • the result of that call shall trigger further widget creation
    • these widget creation cannot happen before your main widget is fully initialized

    Is that all correct ?
    Will you have other widgets created after that ?


Log in to reply