槽在哪个线程中执行,我可以将其重定向到另一个线程吗?

2024-01-16

在了解更多相关知识的同时Qt 中的信号/槽机制 http://doc.qt.io/qt-5/signalsandslots.html,我很困惑插槽在哪个上下文中执行,所以我编写了以下示例来测试它:

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:

主要运行于57474
循环运行在57528
由 57474 用 0 调用的槽
由 57474 用 1 调用的槽
...


到目前为止还不错 - 但现在我发现了塞巴斯蒂安·兰格的回答 https://stackoverflow.com/a/41437798/4464653他说:

你的槽将始终在调用线程中执行,除非你创建一个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()

output:

主要运行于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 https://doc.qt.io/qt-5/qobject.html#moveToThread)?


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

EDIT:
整个问题只是关于QueuedConnection or BlockingQueuedConnection。我知道一个DirectConnection其他选项 https://doc.qt.io/qt-5/qt.html#ConnectionType-enum.


PyQt 中有两种主要类型的槽:一种是包装的 Qt 槽。以及普通的 python 可调用对象。

第一种类型包括 Qt 定义的内置插槽,以及用以下修饰的任何用户定义的插槽pyqtSlot http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html#the-pyqtslot-decorator。这些插槽的工作方式与 Qt 所记录的完全一样,因此没有适用于它们的附加 PyQt 特定“规则”。根据定义,他们必须是QObject子类,这又意味着它们是元对象系统 https://doc.qt.io/qt-5/metaobjects.html。因此,您可以使用例如以下命令明确检查插槽是否属于这种类型:时隙索引 https://doc.qt.io/qt-5/qmetaobject.html#indexOfSlot.

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

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

然而,如果没有合适的接收者,代理将只停留在它创建的线程中。

后一种情况适用于您的示例。这slot_to_output_somethingslot 只是一个没有所有者的模块级函数。 PyQt 找不到与之关联的接收器,因此内部代理将保留在建立连接的线程中。但是,如果此插槽被移动以成为Object_With_A_Signal,它将被称为主线程。这是因为Object_With_A_Signal继承QObject它的实例当前位于主线程中。这允许 PyQt 自动将内部代理移动到适当接收者的线程。

因此,如果您想控制插槽的执行位置,请使其成为QObject子类,并在必要时使用移动到线程 https://doc.qt.io/qt-5/qobject.html#moveToThread显式地将其放置在适当的线程中。此外,可能建议应用pyqtSlot装饰器以避免任何尴尬的极端情况(参见这个答案 https://stackoverflow.com/a/20818401/984421了解详情)。

PS:

上述第二种类型插槽的“规则”可能仅适用于 PyQt - 在 PySide 中不太可能以相同的方式工作。而且也可能无法保证它们将以完全相同的方式与所有以前或未来版本的 PyQt 一起工作。因此,如果您想避免意外的行为变化,最好使用pyqtSlot带有将在不同线程之间连接的任何插槽的装饰器。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

槽在哪个线程中执行,我可以将其重定向到另一个线程吗? 的相关文章

随机推荐