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
QObjectthat 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_somethingmethod isn't triggered at all. The goal is to reach
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_())
def create(self, main_thread):
bridge = ThreadBridge()
bridge is a local variable and is destroyed as soon as create() method terminates...
bridgeis only valid for the lifetime of the
createmethod so it's likely destroyed before it can even start to do something.
nutrx last edited by nutrx
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_())
Yes it is for several reasons:
- you modify a GUI object from a different thread which is something you should not do.
- 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.
- where do I modify GUI? I only modify the controller, no?
- 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.
- My bad ! The code scrolling made me misread what object you were modifying from your thread.
- 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.
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
Do you mean the difference between the zero delay QTimer and using signals and slots ?
nutrx last edited by nutrx
@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
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 ?