【问题标题】:Python lambda functions connected to Qt signals don't run when created in a different thread连接到 Qt 信号的 Python lambda 函数在不同线程中创建时不会运行
【发布时间】:2016-04-28 05:26:23
【问题描述】:

QObject 在另一个线程中创建并使用QObject.moveToThread 移回主线程时,lambda 发出“断开连接”信号(它们不会触发)。我的猜测是常规插槽链接到移动到主线程的QObject,因此它们在主线程的事件循环中运行,但lambda函数没有链接到QObject,所以没有让它们运行的​​事件循环。

这可以在以下简短的 Python 代码中看到:

if __name__ == "__main__":
    from traits.etsconfig.api import ETSConfig
    ETSConfig.toolkit = 'qt4'

import threading
from PySide import QtGui, QtCore

class MyObject(QtCore.QObject):
    def __init__(self, button):
        super(MyObject, self).__init__()
        button.clicked.connect(self.mySlot)
        button.clicked.connect(lambda: self.mySlot('lambda'))
    #
    def mySlot(self, printing='object'):
        print printing
#
myObj = None
# global variable to keep it in memory
def myThread(mainThread, button):
    global myObj
    myObj = MyObject(button)
    myObj.moveToThread(mainThread)
#
if __name__ == '__main__':
    appQT = QtGui.QApplication([])
    #
    myWidget = QtGui.QWidget()
    myButton = QtGui.QPushButton('Press to see output')
    myLayout = QtGui.QVBoxLayout(myWidget)
    myLayout.addWidget(myButton)
    myWidget.show()
    #
    mainThread = QtCore.QThread.currentThread()
    if True:
        # run myThread in a new thread
        # prints only the first slot (object slot)
        threading.Thread(target=myThread, args=[mainThread, myButton]).start()
    else:
        # run myThread in this thread
        # prints both slots (object and lambda slots)
        myThread(mainThread, myButton)
    #
    appQT.exec_()

您可以通过将条件从 True 更改为 False 来查看结果与预期有何不同。

设置为True时,点击按钮后的输出为:

object

设置为False时,点击按钮后的输出为:

object
lambda

我的问题是,有人可以更准确地解释为什么它会以这种方式运行,当将 QObject 移回主线程时,是否有一种简单的方法可以保持 lambda 插槽正常工作?

【问题讨论】:

  • 尝试用lambda self=self: self.mySlot('lambda')替换lambda: self.mySlot('lambda');我认为您遇到了范围问题。
  • 谢谢 chepner,我刚试过,但结果相同,所以这似乎不是问题。
  • 从来没有在Qt中使用过Python,甚至没有过多地使用Python本身,但是你不认为lambda连接是用一些隐藏的QObject实现的吗?尝试在该线程中添加一个事件循环,也许它会出现。此外,您可以使用 GDB 进行探索,Qt C++ 代码非常易于调试,尤其是在您使用 Debian 之类的软件时。
  • 感谢 Velkan 的评论,这将是一个很好的下一步,将 Python 线程更改为 QThreads,看看会发生什么。有机会我会试试这个。

标签: python multithreading qt lambda


【解决方案1】:

好的,我想出了一些关于正在发生的事情的细节。它仍然是部分答案,但我认为它更适合作为答案而不是更新问题。

似乎我在最初的问题中是正确的,即插槽链接到它们的实例对象的 QObject 事件循环(线程),但前提是该插槽是绑定方法(具有对象实例)。

如果您查看PySide source code on Github,您会发现它根据接收到的插槽类型定义了接收器(接收信号的QObject)。因此,如果您向QtSignal.connect() 函数传递一个绑定对象方法,则接收者被定义为slotMethod.__self__(即PyMethod_GET_SELF(callback))。如果您向它传递一个未绑定(没有__self__ 属性)的通用可调用对象(例如lambda 函数),则接收者只需设置为NULL。接收者告诉 Qt 将信号发送到哪个事件循环,所以如果是NULL,它不知道将信号发送到主事件循环。

这是 PySide 源代码的 sn-p:

static bool getReceiver(QObject *source, const char* signal, PyObject* callback, QObject** receiver, PyObject** self, QByteArray* callbackSig)
{
    bool forceGlobalReceiver = false;
    if (PyMethod_Check(callback)) {
        *self = PyMethod_GET_SELF(callback);
        if (%CHECKTYPE[QObject*](*self))
            *receiver = %CONVERTTOCPP[QObject*](*self);
        forceGlobalReceiver = isDecorator(callback, *self);
    } else if (PyCFunction_Check(callback)) {
        *self = PyCFunction_GET_SELF(callback);
        if (*self && %CHECKTYPE[QObject*](*self))
            *receiver = %CONVERTTOCPP[QObject*](*self);
    } else if (PyCallable_Check(callback)) {
        // Ok, just a callable object
        *receiver = 0;
        *self = 0;
    }

    ...
    ...
}

这是否有助于我们解决lambda 函数的问题?不是真的...如果我们使用以下(使用types.MethodType)绑定lambda 函数,则行为不会改变:

import types

class MyObject(QtCore.QObject):
    def __init__(self, button):
        super(MyObject, self).__init__()
        button.clicked.connect(self.mySlot)
        thisLambda = lambda self=self : self.mySlot('hello')
        self.myLambda = types.MethodType( thisLambda, self )
        button.clicked.connect(self.myLambda)
    #
    def mySlot(self, printing='object'):
        print printing

输出:

object

这个绑定肯定是问题的一部分,因为我在下面证明了非绑定全局方法会发生相同的行为,并且通过使用types.MethodType() 绑定它们,它解决了问题:

import types

def abc(self):
    print 'global1'

def xyz(self):
    print 'global2'

class MyObject(QtCore.QObject):
    def __init__(self, button):
        super(MyObject, self).__init__()
        button.clicked.connect(self.mySlot)
        self.xyz = types.MethodType( xyz, self )
        button.clicked.connect(abc)
        button.clicked.connect(self.xyz)
    #
    def mySlot(self, printing='object'):
        print printing

输出:

object
global2

无论如何,似乎最简单的解决方案是首先不在单独的线程中创建 QObject,但这个答案是理解它为什么不能正常工作的一步。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 2012-09-04
    • 2021-09-26
    • 2018-03-06
    • 2021-02-15
    • 1970-01-01
    相关资源
    最近更新 更多