In which thread is a slot executed, and can I redirect it to another thread?

故事扮演 提交于 2019-12-20 04:20:00

问题


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.


回答1:


There are two main types of slot in PyQt: ones which are wrapped Qt slots. and ones which are ordinary python callable objects.

The first type includes the built-in slots defined by Qt, plus any user-defined slots decorated with pyqtSlot. These slots will work exactly as documented by Qt, so there are no additional PyQt-specific "rules" that apply to them. By definition, they must be members of a QObject subclass, which in turn means they are part of the Meta Object System. You can therefore explictly check that a slot is of this type by using, e.g. indexOfSlot.

For the second type of slot, PyQt creates an internal proxy object that wraps the python callable and provides the Qt slot required by the signal-slot mechanism. This therefore raises the question of where this proxy object should live. If the callable is owned by an object that inherits QObject, PyQt can automatically move the proxy to the appropriate thread. In pseudo-code, it will do something like this:

if receiver:
    proxy.moveToThread(receiver.thread())

However, if there's no appropriate reveiver, the proxy will just stay in whatever thread it was created in.

It is this latter case that applies to your example. The slot_to_output_something slot is just a module-level function with no owner. PyQt cannot find a receiver associated with it, so the internal proxy will stay in the thread where the connection was made. However, if this slot was moved to become a member of Object_With_A_Signal, it would be called in the main thread. This is because Object_With_A_Signal inherits QObject and the instance of it currently lives in the main thread. This allows PyQt to automatically move the internal proxy to the thread of the appropriate receiver.

So, if you want to control where a slot gets executed, make it a member of a QObject subclass and, if necessary, use moveToThread to explicitly place it in the appropriate thread. In addition, it is probably advisable to apply the pyqtSlot decorator so as to avoid any awkward corner cases (see this answer for details).

PS:

The above "rules" for the second type of slot probably only apply to PyQt - it's unlikely that things will work the same way in PySide. And there's also probably no guarantee that they will work in exactly the same way with all previous or future versions of PyQt. So if you want to avoid unexpected changes of behaviour, it's best to use the pyqtSlot decorator with any slots that are going to be connected between different threads.



来源:https://stackoverflow.com/questions/51989969/in-which-thread-is-a-slot-executed-and-can-i-redirect-it-to-another-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!