使用 QObject 从 Python 线程发出信号
问题描述
我想知道与 QThread 相比,从 QObject 中的常规 python 线程发出信号的后果是什么.
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.
请参阅以下课程:
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
现在,假设在 GUI 线程中,我有:
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
slot
确实在信号发出时执行.但是,我想知道 slot
方法将在哪个线程中执行?如果我在 MyObject
中使用 QThread
而不是 python 线程,会有什么不同吗?
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
?
我正在使用 PyQt5 和 Python 3.
I am using PyQt5 and Python 3.
解决方案
默认情况下,Qt 信号在跨线程发出时自动排队.为此,它将信号参数序列化,然后将事件发布到接收线程的事件队列,最终将执行任何连接的插槽.因此,以这种方式发出的信号保证是线程安全的.
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.
关于外部线程,Qt 文档声明如下:
注意:Qt 的线程类是用原生线程实现的蜜蜂;例如,Win32 和 pthreads.因此,它们可以与同一原生 API 的线程.
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.
一般来说,如果文档声明 Qt API 是线程安全的,该保证适用于使用同一本机库创建的所有线程 - 而不仅仅是由 Qt 本身创建的线程.这意味着使用诸如 postEvent()
和 调用()
.
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()
.
因此,在发出跨线程信号时,使用 threading.Thread
和 QThread
并没有真正的区别,只要 Python 和 Qt 都适用使用相同的底层原生线程库.这表明在 PyQt 应用程序中更喜欢使用 QThread
的一个可能原因是 可移植性,因为这样就不会有混合不兼容的线程实现的危险.但是,鉴于 Python 和 Qt 都被刻意设计为跨平台,因此在实践中不太可能出现此问题.
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.
关于 slot
将在哪个线程中执行的问题 - 对于 Python 和 Qt,它将在 main 线程中.相比之下,run
方法将在 worker 线程中执行.在 Qt 应用程序中进行多线程处理时,这是一个非常重要的考虑因素,因为在主线程之外执行 gui 操作是不安全的.使用信号可以让您在工作线程和 gui 之间安全地进行通信,因为连接到工作线程发出的信号的插槽将在主线程中被调用,从而允许您在必要时更新那里的 gui.
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_())
输出:
main 140300376593728 MainThread
py:run 140299947104000 Thread-1
py:slot 140300376593728 MainThread
qt:run 140299871450880 Dummy-2
qt:slot 140300376593728 MainThread
相关文章