插槽在哪个线程中执行,我可以将其重定向到另一个线程吗? [英] In which thread is a slot executed, and can I redirect it to another thread?

查看:96
本文介绍了插槽在哪个线程中执行,我可以将其重定向到另一个线程吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在详细了解 Qt中的信号/插槽机制时,我在执行插槽的上下文中感到困惑,因此我编写了以下示例对其进行测试:

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()

输出:

主要在57474中运行
循环在57528中运行
57474使用0调用的广告位
57474用1
调用的插槽 ...

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:

您的插槽将始终在调用线程中执行,除非您创建Qt::QueuedConnection以便在拥有该插槽的对象所属的线程中运行该插槽.

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.

插槽所有权在Python中如何工作?就我的下一个尝试而言,插槽连接到信号的线程是执行插槽的线程,当信号获取时发出:

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()

输出:

主要运行在65804
在65896中运行的辅助线程
循环在65900中运行
65896用0
调用的插槽 65896使用1
调用的插槽 ...

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?

此外,GUI更改应该只在主线程中执行,但是如果我将这些行添加到我的代码中

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 )

输出被重定向到QListWidget中,但表明在主线程中未调用此方法.是否可以将插槽移动到另一个线程(转移所有权"-我刚刚发现

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)?

这是关于使用pyqt执行被调用的插槽(通过发出的信号)的一般规则吗?


整个问题仅与QueuedConnectionBlockingQueuedConnection有关.我知道DirectConnection
其他选项.


This entire question is just about QueuedConnection or BlockingQueuedConnection. I'm aware of a DirectConnection and the other options.

推荐答案

PyQt中的插槽主要有两种类型:一种是封装的Qt插槽.还有那些是普通的python可调用对象.

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

第一种类型包括Qt定义的内置插槽,以及用QObject子类的成员,这又意味着它们是 indexOfSlot .

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.

对于第二种类型的插槽,PyQt创建一个内部代理对象,该对象包装可调用的python并提供信号插槽机制所需的Qt插槽.因此,这提出了该代理对象应位于何处的问题.如果可调用对象由继承QObject的对象所拥有,则PyQt可以自动将代理移动到适当的线程.在伪代码中,它将执行以下操作:

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.

这是后一种情况,适用于您的示例. slot_to_output_something插槽只是没有所有者的模块级功能. PyQt找不到与之关联的接收者,因此内部代理将停留在建立连接的线程中.但是,如果将此插槽移动为Object_With_A_Signal的成员,则会在主线程中调用它.这是因为Object_With_A_Signal继承了QObject,并且其实例当前位于主线程中.这样,PyQt可以将内部代理自动移动到相应接收者的线程中.

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.

因此,如果要控制插槽的执行位置,请使其成为QObject子类的成员,并在必要时使用此答案有关详细信息).

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 :

以上关于第二种插槽的规则"可能仅适用于PyQt-在PySide中不太可能以相同的方式工作.而且也可能无法保证它们将与所有以前或将来的PyQt版本完全相同地工作.因此,如果要避免意外的行为更改,最好将pyqtSlot装饰器与将要在不同线程之间连接的所有插槽一起使用.

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.

这篇关于插槽在哪个线程中执行,我可以将其重定向到另一个线程吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆