我正在使用 Python Qt (PySide2),它在主執行緒中有一個 GUI,在第二個執行緒中有一個執行 IO、資料處理等的庫。
我使用信號和插槽來更新 GUI。在我在 SO 上看到的示例中,信號始終在作業(非 GUI)執行緒中創建。有必要這樣做嗎?
原因:我的庫可以與 GUI 一起使用,也可以在另一個 Python 腳本中使用。因此,它可能會將資料輸出到 GUI 或控制臺/日志檔案。為了使庫中的代碼通用,我認為任何呼叫庫都可以注冊回呼。該回呼可以是emit到 Qt 或輸出到檔案等。
這是一個在 GUI 執行緒中創建 Signal 的示例。它有效,但它會導致與執行緒相關的問題嗎?
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()
uj5u.com熱心網友回復:
默認情況下,使用AutoConnection型別進行信號連接。這意味著最終的連接型別將在實際發出信號時在運行時自動建立。如果發送者和接收者在不同的執行緒中,Qt 將使用佇列連接,它將一個事件發布到接收執行緒的事件佇列中。因此,當控制權回傳到接收器時,將處理該事件并且屆時將呼叫任何連接的插槽。
當從作業執行緒更新 GUI 元素時,這是一個重要的考慮因素,因為 Qt 不支持主執行緒之外的任何型別的 GUI 相關操作。但是,只要您始終使用信號在使用默認連接型別的執行緒之間進行通信,就不會有任何問題 - Qt 會自動保證它們以執行緒安全的方式完成。
以下是您的腳本版本,可驗證一切是否按預期作業。當我運行它時,我得到以下輸出,它顯示了每個函式呼叫中的當前執行緒 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()
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/442908.html
