Python Qt - 是否需要在工作线程中创建信号?
Python Qt - do signals need to be created in the worker thread?
我正在使用 Python Qt (PySide2),它在主线程中有一个 GUI,在第二个线程中有一个执行 IO、数据处理等的库。
我使用信号和槽来更新 GUI。在我在 SO 上看到的示例中,信号始终在工作线程(非 GUI)中创建。有必要这样做吗?
原因:我的库可以与 GUI 一起使用,也可以在另一个 Python 脚本中使用。因此,它可能会将数据输出到 GUI 或者 console/log 文件。为了使库中的代码通用,我认为无论调用库都可以注册一个回调。该回调可以是 emit
到 Qt 或输出到文件等
这是一个在 GUI 线程中创建信号的示例。它有效,但它会导致与线程相关的问题吗?
import threading
import time
import sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication
from PySide2 import QtCore
class aio_connection(QtCore.QObject):
data_recvd = QtCore.Signal(object)
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.aio_conn = aio_connection() # Signal is created in main (GUI) thread
# Connect signals (data_recvd) and slots (on_data_ready)
self.aio_conn.data_recvd.connect(self.on_data_ready)
def btnPress1_Clicked(self):
threading.Thread(target=bg_task, args=(self.cb,)).start()
@QtCore.Slot()
def on_data_ready(self, msg):
self.textEdit.append(msg)
def cb(self, info):
self.aio_conn.data_recvd.emit(info)
# Simulate the library that runs in a second thread
def bg_task(callback):
for i in range(100):
callback(str(i))
time.sleep(0.1)
def main():
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
默认情况下,信号连接使用AutoConnection类型。这意味着最终类型的连接将在 运行time 时自动建立,当信号实际发出时。如果发送者和接收者在不同的线程中,Qt 将使用排队连接,这将 post 事件发送到接收线程的 event-queue。因此,当控制 returns 到接收者时,事件将被处理,任何连接的槽都将在那时被调用。
从工作线程更新 GUI 元素时,这是一个重要的考虑因素,因为 Qt 不支持 GUI-related 主线程之外的任何类型的操作。但是,只要您始终使用信号在使用默认 connection-type 的线程之间进行通信,您就不会有任何问题 - Qt 会自动保证它们以 thread-safe 方式完成。
以下是您的脚本版本,可验证一切是否按预期运行。当我 运行 它时,我得到以下输出,显示每个函数调用中的当前 thread-id:
main: 4973
bg_task: 4976
cb: 4976
on_data_ready: 4973
cb: 4976
on_data_ready: 4973
cb: 4976
...
import threading, time, sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication
from PySide2 import QtCore
class aio_connection(QtCore.QObject):
data_recvd = QtCore.Signal(object)
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.aio_conn = aio_connection()
self.aio_conn.data_recvd.connect(self.on_data_ready)
def btnPress1_Clicked(self):
threading.Thread(target=bg_task, args=(self.cb,)).start()
@QtCore.Slot()
def on_data_ready(self, msg):
print('on_data_ready:', threading.get_native_id())
self.textEdit.append(msg)
def cb(self, info):
print('cb:', threading.get_native_id())
self.aio_conn.data_recvd.emit(info)
def bg_task(callback):
print('bg_task:', threading.get_native_id())
for i in range(5):
callback(str(i))
time.sleep(0.1)
def main():
print('main:', threading.get_native_id())
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
我正在使用 Python Qt (PySide2),它在主线程中有一个 GUI,在第二个线程中有一个执行 IO、数据处理等的库。
我使用信号和槽来更新 GUI。在我在 SO 上看到的示例中,信号始终在工作线程(非 GUI)中创建。有必要这样做吗?
原因:我的库可以与 GUI 一起使用,也可以在另一个 Python 脚本中使用。因此,它可能会将数据输出到 GUI 或者 console/log 文件。为了使库中的代码通用,我认为无论调用库都可以注册一个回调。该回调可以是 emit
到 Qt 或输出到文件等
这是一个在 GUI 线程中创建信号的示例。它有效,但它会导致与线程相关的问题吗?
import threading
import time
import sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication
from PySide2 import QtCore
class aio_connection(QtCore.QObject):
data_recvd = QtCore.Signal(object)
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.aio_conn = aio_connection() # Signal is created in main (GUI) thread
# Connect signals (data_recvd) and slots (on_data_ready)
self.aio_conn.data_recvd.connect(self.on_data_ready)
def btnPress1_Clicked(self):
threading.Thread(target=bg_task, args=(self.cb,)).start()
@QtCore.Slot()
def on_data_ready(self, msg):
self.textEdit.append(msg)
def cb(self, info):
self.aio_conn.data_recvd.emit(info)
# Simulate the library that runs in a second thread
def bg_task(callback):
for i in range(100):
callback(str(i))
time.sleep(0.1)
def main():
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
默认情况下,信号连接使用AutoConnection类型。这意味着最终类型的连接将在 运行time 时自动建立,当信号实际发出时。如果发送者和接收者在不同的线程中,Qt 将使用排队连接,这将 post 事件发送到接收线程的 event-queue。因此,当控制 returns 到接收者时,事件将被处理,任何连接的槽都将在那时被调用。
从工作线程更新 GUI 元素时,这是一个重要的考虑因素,因为 Qt 不支持 GUI-related 主线程之外的任何类型的操作。但是,只要您始终使用信号在使用默认 connection-type 的线程之间进行通信,您就不会有任何问题 - Qt 会自动保证它们以 thread-safe 方式完成。
以下是您的脚本版本,可验证一切是否按预期运行。当我 运行 它时,我得到以下输出,显示每个函数调用中的当前 thread-id:
main: 4973
bg_task: 4976
cb: 4976
on_data_ready: 4973
cb: 4976
on_data_ready: 4973
cb: 4976
...
import threading, time, sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QApplication
from PySide2 import QtCore
class aio_connection(QtCore.QObject):
data_recvd = QtCore.Signal(object)
class TextEditDemo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("TEST")
self.resize(600,540)
self.textEdit = QTextEdit()
self.btnPress1 = QPushButton("Run")
layout = QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btnPress1)
self.setLayout(layout)
self.btnPress1.clicked.connect(self.btnPress1_Clicked)
self.aio_conn = aio_connection()
self.aio_conn.data_recvd.connect(self.on_data_ready)
def btnPress1_Clicked(self):
threading.Thread(target=bg_task, args=(self.cb,)).start()
@QtCore.Slot()
def on_data_ready(self, msg):
print('on_data_ready:', threading.get_native_id())
self.textEdit.append(msg)
def cb(self, info):
print('cb:', threading.get_native_id())
self.aio_conn.data_recvd.emit(info)
def bg_task(callback):
print('bg_task:', threading.get_native_id())
for i in range(5):
callback(str(i))
time.sleep(0.1)
def main():
print('main:', threading.get_native_id())
app = QApplication(sys.argv)
win = TextEditDemo()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()