Search code examples
python-3.xqtqt5pyside2pyside6

Easy way to call a function on the main thread in Qt (PySide2)


Is there any easy way to call a function or method in the main thread from any other thread or QThread?

I heard that Slots and Signals can be used as a proxy between a thread and the main thread but it feels like too much work to create such a proxy for every single time I want to transfer data to my main thread.

(My answer describes a very universal way to acclompish this so I won't be providing a "minimal" example here, you can look at the answer.)


Solution

  • Qt has a function called invokeMethod (https://doc.qt.io/qt-5/qmetaobject.html#invokeMethod) that can be used to invoke a method on the main thread by using the Qt.QueuedConnection connection type.

    In PySide however this won't work if you want to call a function with arguments!

    Solution:

    So to allow a similar and even easier functionality with PySide, I have written this class which can be used every time you want to run a function or method on the main thread:

    from typing import Callable
    
    from PySide2.QtCore import QObject, Signal, Slot
    from PySide2.QtGui import QGuiApplication
    
    
    class InvokeMethod(QObject):
        def __init__(self, method: Callable):
            """
            Invokes a method on the main thread. Taking care of garbage collection "bugs".
            """
            super().__init__()
    
            main_thread = QGuiApplication.instance().thread()
            self.moveToThread(main_thread)
            self.setParent(QGuiApplication.instance())
            self.method = method
            self.called.connect(self.execute)
            self.called.emit()
    
        called = Signal()
    
        @Slot()
        def execute(self):
            self.method()
            # trigger garbage collector
            self.setParent(None)
    

    This will internally create a Signal and a Slot without any parameters. The Slot however will be called on the main thread as it has been connected using Qt.AutoConnection (the default) and moved to the main thread with moveToThread(...). To make sure no function arguments get lost due to the garbage collector, the parent of the class is temporarily set to the QGuiApplication instance (you might need to change this if you don't rely on the QGuiApplication. Any QObject will be fine as the parent).

    Here is an example on how to use this class:

    InvokeMethod(lambda: print("hello"))