Emitting signals from a Python thread using QObject

故事扮演 提交于 2019-12-01 04:05:25

问题


I would like to know what are the consequences of emitting a signal from a regular python thread within a QObject, compared with a QThread.

See the following class:

class MyObject(QtCore.QObject):

    def __init__(self):
        super().__init__()

    sig = pyqtSignal()

    def start(self):
        self._thread = Thread(target=self.run)
        self._thread.start()

    def run(self):
        self.sig.emit()
        # Do something

Now, assuming that in the GUI thread, I have:

def __init__(self):
    self.obj = MyObject()
    self.obj.sig.connect(self.slot)
    self.obj.start()

def slot(self):
    # Do something

the slot is indeed executed when the signal is emitted. However, I would like to know which thread will the slot method be executed in? Would it be any different if I used a QThread instead of a python thread in MyObject?

I am using PyQt5 and Python 3.


回答1:


By default, Qt automatically queues signals when they are emitted across threads. To do this, it serializes the signal parameters and then posts an event to the event-queue of the receiving thread, where any connected slots will eventually be executed. Signals emitted in this way are therefore guaranteed to be thread-safe.

With regard to external threads, the Qt docs state the following:

Note: Qt's threading classes are implemented with native threading APIs; e.g., Win32 and pthreads. Therefore, they can be used with threads of the same native API.

In general, if the docs state that a Qt API is thread-safe, that guarantee applies to all threads that were created using the same native library - not just the ones that were created by Qt itself. This means it is also safe to explicitly post events to other threads using such thread-safe APIs as postEvent() and invoke().

There is therefore no real difference between using threading.Thread and QThread when it comes to emitting cross-thread signals, so long as both Python and Qt use the same underlying native threading library. This suggests that one possible reason to prefer using QThread in a PyQt application is portability, since there will then be no danger of mixing incompatible threading implementations. However, it is highly unlikely that this issue will ever arise in practice, given that both Python and Qt are deliberately designed to be cross-platform.


As to the question of which thread the slot will be executed in - for both Python and Qt, it will be in the main thread. By contrast, the run method will be executed in the worker thread. This is a very important consideration when doing multi-threading in a Qt application, because it is not safe to perform gui operations outside the main thread. Using signals allows you to safely communicate between the worker thread and the gui, because the slot connected to the signal emitted from the worker will be called in the main thread, allowing you to update the gui there if necessary.

Below is a simple script that shows which thread each method is called in:

import sys, time, threading
from PyQt5 import QtCore, QtWidgets

def thread_info(msg):
    print(msg, int(QtCore.QThread.currentThreadId()),
          threading.current_thread().name)

class PyThreadObject(QtCore.QObject):
    sig = QtCore.pyqtSignal()

    def start(self):
        self._thread = threading.Thread(target=self.run)
        self._thread.start()

    def run(self):
        time.sleep(1)
        thread_info('py:run')
        self.sig.emit()

class QtThreadObject(QtCore.QThread):
    sig = QtCore.pyqtSignal()

    def run(self):
        time.sleep(1)
        thread_info('qt:run')
        self.sig.emit()

class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.pyobj = PyThreadObject()
        self.pyobj.sig.connect(self.pyslot)
        self.pyobj.start()
        self.qtobj = QtThreadObject()
        self.qtobj.sig.connect(self.qtslot)
        self.qtobj.start()

    def pyslot(self):
        thread_info('py:slot')

    def qtslot(self):
        thread_info('qt:slot')

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(600, 100, 300, 200)
    window.show()
    thread_info('main')
    sys.exit(app.exec_())

Output:

main 140300376593728 MainThread
py:run 140299947104000 Thread-1
py:slot 140300376593728 MainThread
qt:run 140299871450880 Dummy-2
qt:slot 140300376593728 MainThread



回答2:


I would like to add:

class MyQThread(QThread):
    signal = pyqtSignal() # This thread emits this at some point.

class MainThreadObject(QObject):
    def __init__(self):
        thread = MyQThread()
        thread.signal.connect(self.mainThreadSlot)
        thread.start()

    @pyqtSlot()
    def mainThreadSlot(self):
        pass

This is perfectly OK, according to all documentation I know of. As is the following:

class MyQObject(QObject):
    signal = pyqtSignal()

class MainThreadObject(QObject):
    def __init__(self):
        self.obj = MyQObject()
        self.obj.signal.connect(self.mainThreadSlot)
        self.thread = threading.Thread(target=self.callback)
        self.thread.start()

    def callback(self):
        self.obj.signal.emit()

    @pyqtSlot()
    def mainThreadSlot(self):
        pass

From what @ekhumoro is saying, those two are functionally the same thing. Because a QThread is just a QObject who's run() method is the target= of a threading.Thread.

In other words, both the MyQThread's and the MyQObject's signal is memory "owned" by the main thread, but accessed from child threads.

Therefore the following should also be safe:

class MainThreadObject(QObject):
    signal = pyqtSignal() # Connect to this signal from QML or Python

    def __init__(self):
        self.thread = threading.Thread(target=self.callback)
        self.thread.start()

    def callback(self):
        self.signal.emit()

Please correct me if I am wrong. It would be very nice to have official documentation on this behavior from Qt and/or Riverbank.



来源:https://stackoverflow.com/questions/30676599/emitting-signals-from-a-python-thread-using-qobject

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!