While learning more about the Signal/Slot mechanic in Qt, I was confused in which context a slot is executed, so I wrote the following example to test it:
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()
output:
main running in 57474
loop running in 57528
slot called by 57474 with 0
slot called by 57474 with 1
...
pretty fine so far - but now I found the answer of Sebastian Lange where he said:
Your slot will always be executed in the calling thread, except you create a
Qt::QueuedConnection
to run the slot in the thread the object owning the slot belongs to.
How is ownership of slots working in Python? As far as I my next try shows, the thread where a slot gets connected to a signal is the thread that executes the slot, when the signal gets emited:
# 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()
output:
main running in 65804
helper thread running in 65896
loop running in 65900
slot called by 65896 with 0
slot called by 65896 with 1
...
So .. am I rigth? Are Slots excuted by the Thread, in which they get connected or did I just came up with a bad example?
Furthermore, GUI-changes should just be executed in the main-thread, but if I add these lines to my code
# use QListwidget for output instead
lis = QListWidget( None )
print = lambda *args: lis.addItem( str( ' '.join( str( x ) for x in args ) ) )
mainw.setCentralWidget( lis )
the output gets redirected into a QListWidget, but shows that this is not called in the main-thread. Is there an option to move the slot to another thread (transfering "ownership" - I just found QObject::moveToThread
)?
Is their a general rule about the execution of called slots (by emited signals) with pyqt?
EDIT:
This entire question is just about QueuedConnection
or BlockingQueuedConnection
. I'm aware of a DirectConnection
and the other options.