在深入了解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。
答案 0 :(得分:2)
PyQt中有两种主要的插槽类型:包裹Qt插槽的插槽。还有那些是普通的python可调用对象。
第一种类型包括Qt定义的内置插槽,以及任何用pyqtSlot
装饰的用户定义的插槽。这些插槽将完全按照Qt的说明工作,因此没有适用于它们的其他PyQt特定的“规则”。根据定义,它们必须是QObject
子类的成员,这又意味着它们是Meta Object System的一部分。因此,您可以使用以下方式明确检查某个插槽是否属于此类: indexOfSlot。
对于第二种插槽,PyQt创建一个内部代理对象,该对象包装可调用的python,并提供信号插槽机制所需的Qt插槽。因此,这提出了该代理对象应位于何处的问题。如果可调用对象由继承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
装饰器与将要在不同线程之间连接的所有插槽一起使用。