插槽在哪个线程中执行,我可以将其重定向到另一个线程吗?
Posted
技术标签:
【中文标题】插槽在哪个线程中执行,我可以将其重定向到另一个线程吗?【英文标题】:In which thread is a slot executed, and can I redirect it to another thread? 【发布时间】:2019-01-30 01:54:10 【问题描述】:在学习更多关于Signal/Slot mechanic in Qt的过程中,我很困惑一个槽是在哪个上下文中执行的,所以我写了下面的例子来测试它:
from PyQt5.Qt import * # I know this is bad, but I want a small example
import threading
def slot_to_output_something ( something ):
print( 'slot called by', threading.get_ident(), 'with', something )
class Object_With_A_Signal( QObject ):
sig = pyqtSignal( str )
class LoopThread( QThread ):
def __init__ ( self, object_with_a_signal ):
self.object_with_a_signal = object_with_a_signal
super().__init__()
def run ( self ):
print( 'loop running in', threading.get_ident() )
import time
for i in range( 5 ):
self.object_with_a_signal.sig.emit( str( i ) )
time.sleep( 1 )
print( 'main running in', threading.get_ident() )
app = QApplication( [] )
mainw = QMainWindow( None )
mainw.show()
obj = Object_With_A_Signal()
# connection in main-thread
obj.sig.connect(slot_to_output_something, Qt.QueuedConnection )
loop = LoopThread( obj )
loop.start()
app.exec()
输出:
主要运行在 57474 循环在 57528 中运行 由 57474 以 0 调用的插槽 插槽由 57474 调用,带有 1 ...
到目前为止还不错 - 但现在我找到了answer of Sebastian Lange,他说:
您的插槽将始终在调用线程中执行,除非您创建一个
Qt::QueuedConnection
来在拥有该插槽的对象所属的线程中运行该插槽。
槽的所有权在 Python 中是如何工作的? 就我的下一次尝试显示,槽连接到信号的线程是执行槽的线程,当信号获取时发出:
# connection in main-thread
# obj.sig.connect(slot_to_output_something, Qt.QueuedConnection )
# loop = LoopThread( obj )
# loop.start()
# connection in helper-thread
class Thread_In_Between( QThread ):
def __init__ ( self, object_with_a_signal ):
super().__init__()
self.object_with_a_signal = object_with_a_signal
def run ( self ):
print( 'helper thread running in', threading.get_ident() )
self.object_with_a_signal.sig.connect( slot_to_output_something, Qt.QueuedConnection)
loop = LoopThread( self.object_with_a_signal )
loop.start()
loop.exec() # without -> ERROR: QThread: Destroyed while thread is still running
print( 'end helper thread' ) # never reached ??
helper_thread = Thread_In_Between( obj )
helper_thread.start()
输出:
主要运行在 65804 在 65896 中运行的辅助线程 循环运行在 65900 插槽由 65896 调用,带有 0 插槽由 65896 调用,带有 1 ...
所以..我说得对吗? 插槽是由线程执行的,它们在其中连接还是我只是想出了一个不好的例子?
此外,GUI 更改应该只在主线程中执行,但如果我将这些行添加到我的代码中
# use QListwidget for output instead
lis = QListWidget( None )
print = lambda *args: lis.addItem( str( ' '.join( str( x ) for x in args ) ) )
mainw.setCentralWidget( lis )
输出被重定向到一个 QListWidget,但表明它没有在主线程中调用。是否可以选择将插槽移动到另一个线程(转移“所有权” - 我刚刚找到 QObject::moveToThread
)?
它们是关于使用 pyqt 执行调用槽(通过发出的信号)的一般规则吗?
编辑:
这整个问题只是关于QueuedConnection
或BlockingQueuedConnection
。我知道DirectConnection
和other options。
【问题讨论】:
为什么要在另一个线程中创建线程?循环被破坏,因为它是一个局部变量 否 - 使用QueuedConnection,槽在接收者的线程中执行。 @eyllanesc:我创建了另一个线程来查看,如果他是执行该插槽的人,情况确实如此。此外,loop
不会被破坏,因为代码不会越过loop.exec()
行,它不会离开事件循环(不知道为什么?)。
@ekhumoro: 并且是他们的一套规则,决定了谁是接收线程?
【参考方案1】:
PyQt 中有两种主要类型的插槽:一种是封装的 Qt 插槽。以及普通的python可调用对象。
第一种类型包括 Qt 定义的内置槽,以及任何用pyqtSlot
修饰的用户定义槽。这些插槽将完全按照 Qt 记录的方式工作,因此没有其他适用于它们的 PyQt 特定“规则”。根据定义,它们必须是QObject
子类的成员,这反过来意味着它们是Meta Object System 的一部分。因此,您可以通过使用例如明确地检查插槽是否属于这种类型。 indexOfSlot.
对于第二种类型的槽,PyQt 创建了一个内部代理对象,该对象包装了 python 可调用对象,并提供了信号槽机制所需的 Qt 槽。因此,这就提出了这个代理对象应该在哪里生活的问题。如果 callable 由继承 QObject
的对象拥有,PyQt 可以自动将代理移动到适当的线程。在伪代码中,它会做这样的事情:
if receiver:
proxy.moveToThread(receiver.thread())
但是,如果没有合适的接收者,代理将停留在创建它的任何线程中。
后一种情况适用于您的示例。 slot_to_output_something
插槽只是一个没有所有者的模块级函数。 PyQt 找不到与之关联的接收器,因此内部代理将留在建立连接的线程中。然而,如果这个槽被移动成为Object_With_A_Signal
的成员,它将在主线程中被调用。这是因为Object_With_A_Signal
继承了QObject
并且它的实例当前位于主线程中。这允许 PyQt 自动将内部代理移动到相应接收器的线程。
因此,如果您想控制插槽在何处执行,请将其设为QObject
子类的成员,并在必要时使用moveToThread 将其显式放置在适当的线程中。此外,建议使用 pyqtSlot
装饰器以避免任何尴尬的极端情况(详见this answer)。
PS:
上述第二类插槽的“规则”可能仅适用于 PyQt - 在 PySide 中事情不太可能以相同的方式工作。并且也可能无法保证它们与所有以前或未来版本的 PyQt 的工作方式完全相同。因此,如果您想避免意外的行为变化,最好将 pyqtSlot
装饰器与将在不同线程之间连接的任何插槽一起使用。
【讨论】:
以上是关于插槽在哪个线程中执行,我可以将其重定向到另一个线程吗?的主要内容,如果未能解决你的问题,请参考以下文章
Pharo Smalltalk - 消息转发,是不是可以截获消息并将其重定向到另一个对象(实例)?