如何在 PyQt 中使用双向 QThreads 设置信号和插槽?
How do I setup Signals and Slots in PyQt with QThreads in both directions?
这是基于 ekhumoro 回答的后续问题 and here。
我想明白了,当一个插槽被正确定义为 pyqtSlot
并分配给 QThread
(例如 moveToThread()
)时,它将在这个 QThread 中执行,而不是打电话的那个此外,还需要与 Qt.QueuedConnection
或 Qt.AutoConnection
建立联系。
我写代码来测试这个。我的目标是实现一些非常简单的事情:
带有按钮的 Gui,开始一些耗时的工作,return 结果显示在 GUI 中。
from PyQt5.Qt import *
class MainWindow(QMainWindow):
change_text = pyqtSignal(str)
def __init__(self):
super().__init__()
self.button = QPushButton('Push me!', self)
self.setCentralWidget(self.button)
print('main running in:', QThread.currentThread())
thread = Thread(change_text, self)
thread.start()
self.button.clicked.connect( thread.do_something_slow, Qt.QueuedConnection)
self.change_text.connect(self.display_changes, Qt.QueuedConnection)
@pyqtSlot(str)
def display_changes( self, text ):
self.button.setText(text)
class Thread(QThread):
def __init__(self, signal_to_emit, parent):
super().__init__(parent)
self.signal_to_emit = signal_to_emit
#self.moveToThread(self) #doesn't help
@pyqtSlot()
def do_something_slow( self ):
print('Slot doing stuff in:', QThread.currentThread())
import time
time.sleep(5)
self.signal_to_emit.emit('I did something')
if __name__ == '__main__':
app = QApplication([])
main = MainWindow()
main.show()
app.exec()
但是 ..gui 正在阻塞并且插槽在主线程中被调用。
我错过了什么?一定要小(我希望)。
问题是你混淆了QThread
是一个Qt线程,也就是Qt创建的新线程,但是不是,QThread
是 处理 本机线程的 class,只有 run()
方法在另一个线程上 运行ning,其他方法位于线程中QThread
条命,也就是 QObject
,
QObject 存在于哪个线程中?
QObject
所在的线程是父线程,如果它没有父线程,它将是创建它的线程。另一方面,QObject
可以使用 moveToThread()
移动到另一个线程,它的所有子线程也将移动。如果 QObject
没有父级,则只能使用 moveToThread()
,否则会失败。
使用 QThread
的方法是创建一个继承自 QThread
的 class 并覆盖 运行 方法并调用 start()
以便它starts 运行ning run()
,在run()
方法中会完成繁重的任务,但在你的情况下不能使用这种形式,因为任务不会连续执行。比繁重的任务更好的选择是 QObject
的一部分,然后 QObject
将其移动到另一个线程。
class MainWindow(QMainWindow):
change_text = pyqtSignal(str)
def __init__(self):
super().__init__()
self.button = QPushButton('Push me!', self)
self.setCentralWidget(self.button)
print('main running in:', QThread.currentThread())
# A Worker without a parent is created
# so that it can be moved to another thread.
self.worker = Worker(self.change_text)
thread = QThread(self)
self.worker.moveToThread(thread)
thread.start()
# All methods of self.worker are now executed in another thread.
self.button.clicked.connect(self.worker.do_something_slow)
self.change_text.connect(self.display_changes)
@pyqtSlot(str)
def display_changes( self, text ):
self.button.setText(text)
class Worker(QObject):
def __init__(self, signal_to_emit, parent=None):
super().__init__(parent)
self.signal_to_emit = signal_to_emit
@pyqtSlot()
def do_something_slow( self ):
print('Slot doing stuff in:', QThread.currentThread())
import time
time.sleep(5)
self.signal_to_emit.emit('I did something')
另一方面,没有必要指明连接类型,因为默认情况下它是 Qt::AutoConnection
,如果您使用 Qt::DirectConnection
,这种连接类型将在 运行 时间内决定如果接收器与信号发射处位于同一根电线中,则使用 Qt::QueuedConnection
。如您所知,Worker 没有父级,因此要使它的生命周期等于 class,它必须是它的一个属性,否则它将是一个被消除的局部变量。另一方面,请注意 QThread
接收父窗口而不是 MainWindow,因此 QThread
将存在于 GUI 线程中,但它处理辅助线程。
有了 Worker 的概念,change_text 信号最好不再属于 GUI,而是更好地解耦对象的 worker。
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.button = QPushButton('Push me!', self)
self.setCentralWidget(self.button)
print('main running in:', QThread.currentThread())
self.worker = Worker()
thread = QThread(self)
self.worker.moveToThread(thread)
thread.start()
self.button.clicked.connect(self.worker.do_something_slow)
self.worker.change_text.connect(self.display_changes)
@pyqtSlot(str)
def display_changes( self, text ):
self.button.setText(text)
class Worker(QObject):
change_text = pyqtSignal(str)
@pyqtSlot()
def do_something_slow( self ):
print('Slot doing stuff in:', QThread.currentThread())
import time
time.sleep(5)
self.change_text.emit('I did something')
这是基于 ekhumoro 回答的后续问题
我想明白了,当一个插槽被正确定义为 pyqtSlot
并分配给 QThread
(例如 moveToThread()
)时,它将在这个 QThread 中执行,而不是打电话的那个此外,还需要与 Qt.QueuedConnection
或 Qt.AutoConnection
建立联系。
我写代码来测试这个。我的目标是实现一些非常简单的事情:
带有按钮的 Gui,开始一些耗时的工作,return 结果显示在 GUI 中。
from PyQt5.Qt import *
class MainWindow(QMainWindow):
change_text = pyqtSignal(str)
def __init__(self):
super().__init__()
self.button = QPushButton('Push me!', self)
self.setCentralWidget(self.button)
print('main running in:', QThread.currentThread())
thread = Thread(change_text, self)
thread.start()
self.button.clicked.connect( thread.do_something_slow, Qt.QueuedConnection)
self.change_text.connect(self.display_changes, Qt.QueuedConnection)
@pyqtSlot(str)
def display_changes( self, text ):
self.button.setText(text)
class Thread(QThread):
def __init__(self, signal_to_emit, parent):
super().__init__(parent)
self.signal_to_emit = signal_to_emit
#self.moveToThread(self) #doesn't help
@pyqtSlot()
def do_something_slow( self ):
print('Slot doing stuff in:', QThread.currentThread())
import time
time.sleep(5)
self.signal_to_emit.emit('I did something')
if __name__ == '__main__':
app = QApplication([])
main = MainWindow()
main.show()
app.exec()
但是 ..gui 正在阻塞并且插槽在主线程中被调用。
我错过了什么?一定要小(我希望)。
问题是你混淆了QThread
是一个Qt线程,也就是Qt创建的新线程,但是不是,QThread
是 处理 本机线程的 class,只有 run()
方法在另一个线程上 运行ning,其他方法位于线程中QThread
条命,也就是 QObject
,
QObject 存在于哪个线程中?
QObject
所在的线程是父线程,如果它没有父线程,它将是创建它的线程。另一方面,QObject
可以使用 moveToThread()
移动到另一个线程,它的所有子线程也将移动。如果 QObject
没有父级,则只能使用 moveToThread()
,否则会失败。
使用 QThread
的方法是创建一个继承自 QThread
的 class 并覆盖 运行 方法并调用 start()
以便它starts 运行ning run()
,在run()
方法中会完成繁重的任务,但在你的情况下不能使用这种形式,因为任务不会连续执行。比繁重的任务更好的选择是 QObject
的一部分,然后 QObject
将其移动到另一个线程。
class MainWindow(QMainWindow):
change_text = pyqtSignal(str)
def __init__(self):
super().__init__()
self.button = QPushButton('Push me!', self)
self.setCentralWidget(self.button)
print('main running in:', QThread.currentThread())
# A Worker without a parent is created
# so that it can be moved to another thread.
self.worker = Worker(self.change_text)
thread = QThread(self)
self.worker.moveToThread(thread)
thread.start()
# All methods of self.worker are now executed in another thread.
self.button.clicked.connect(self.worker.do_something_slow)
self.change_text.connect(self.display_changes)
@pyqtSlot(str)
def display_changes( self, text ):
self.button.setText(text)
class Worker(QObject):
def __init__(self, signal_to_emit, parent=None):
super().__init__(parent)
self.signal_to_emit = signal_to_emit
@pyqtSlot()
def do_something_slow( self ):
print('Slot doing stuff in:', QThread.currentThread())
import time
time.sleep(5)
self.signal_to_emit.emit('I did something')
另一方面,没有必要指明连接类型,因为默认情况下它是 Qt::AutoConnection
,如果您使用 Qt::DirectConnection
,这种连接类型将在 运行 时间内决定如果接收器与信号发射处位于同一根电线中,则使用 Qt::QueuedConnection
。如您所知,Worker 没有父级,因此要使它的生命周期等于 class,它必须是它的一个属性,否则它将是一个被消除的局部变量。另一方面,请注意 QThread
接收父窗口而不是 MainWindow,因此 QThread
将存在于 GUI 线程中,但它处理辅助线程。
有了 Worker 的概念,change_text 信号最好不再属于 GUI,而是更好地解耦对象的 worker。
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.button = QPushButton('Push me!', self)
self.setCentralWidget(self.button)
print('main running in:', QThread.currentThread())
self.worker = Worker()
thread = QThread(self)
self.worker.moveToThread(thread)
thread.start()
self.button.clicked.connect(self.worker.do_something_slow)
self.worker.change_text.connect(self.display_changes)
@pyqtSlot(str)
def display_changes( self, text ):
self.button.setText(text)
class Worker(QObject):
change_text = pyqtSignal(str)
@pyqtSlot()
def do_something_slow( self ):
print('Slot doing stuff in:', QThread.currentThread())
import time
time.sleep(5)
self.change_text.emit('I did something')