Python 连接到Qt信号don';在不同的线程中创建时不会运行
当在不同的线程中创建Python 连接到Qt信号don';在不同的线程中创建时不会运行,python,multithreading,qt,lambda,Python,Multithreading,Qt,Lambda,当在不同的线程中创建QObject并使用QObject.moveToThread将其移回主线程时,lambda发出“断开连接”的信号(它们不会触发)。我的猜测是,常规插槽链接到移动到主线程的QObject,因此它们在主线程的事件循环中运行,但是lambda函数没有链接到QObject,因此它们没有要运行的事件循环 这可以在以下简短的Python代码中看到: if __name__ == "__main__": from traits.etsconfig.api import ETSCon
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
object
lambda
当设置为False
时,单击按钮后的输出为:
object
object
lambda
我的问题是,是否有人能更准确地解释它为何会以这种方式运行,以及在将QObject
移回主线程时是否有一种简单的方法来保持lambda
插槽工作?好的,我了解了一些有关发生了什么的细节。这仍然是一个部分答案,但我认为它更适合作为一个答案,而不是更新的问题
在我最初的问题中,我似乎是正确的,即插槽链接到其实例对象的QObject
事件循环(线程),但前提是该插槽是绑定方法(具有对象实例)
如果查看,您将看到它根据接收的插槽类型定义接收器(接收信号的QObject
)。因此,如果您将QtSignal.connect()
函数传递给绑定对象方法,则接收器被定义为(即PyMethod\u GET\u SELF(回调)
)。如果您向它传递一个未绑定的通用可调用对象(例如lambda
函数)(无\uuuuuu self\uuuuu
属性),则接收器只需设置为NULL
。接收器告诉Qt要将信号发送到哪个事件循环,因此如果它是NULL
,它不知道要将信号发送到主事件循环
以下是PySide源代码的一个片段:
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
object
global2
这种绑定肯定是问题的一部分,因为我在下面演示了非绑定全局方法也会出现相同的行为,通过使用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
object
global2
无论如何,最简单的解决方案似乎就是首先不要在单独的线程中创建QObject,但这个答案是理解它为什么不能正常工作的一个步骤。尝试用
lambda-self=self:self.mySlot('lambda')
替换lambda-self=self.mySlot('lambda')
;我认为您遇到了范围问题。谢谢chepner,我刚刚尝试了它,但它给出了相同的结果,所以这似乎不是问题所在。从未在Qt中使用Python,甚至Python本身使用得太多,但您不认为lambda连接是通过一些隐藏的QObject实现的吗?尝试将事件循环添加到该线程中,它可能会出现。另外,你可以用GDB来探索它,Qt C++代码非常可调试,特别是如果你在Debian之类的东西上。谢谢Velkan的评论,这是一个很好的下一步,把Python线程变成QPosits,看看会发生什么。有机会的话我会试试的。