如果从后台线程调用私有槽,为什么主事件循环会被冻结?

时间:2014-11-01 13:11:31

标签: python multithreading qt pyside slot

我正在使用python和PySide开发GUI应用程序。我需要在一个单独的线程中运行长背景任务。对于线程,我决定根据'正确'方法使用QThread,而不从if(参见http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/)继承。因此,为了在后台运行我的长任务,我只需将线程的信号'started'连接到一个执行长任务的方法。

出于某种原因,我的程序没有按预期工作。我花了几个小时才发现如果我将插槽名称重命名为'public'(删除下划线),一切都会按预期开始工作。我想知道私人插槽有什么问题?似乎由于某种原因,主事件循环在私有插槽正在进行时不起作用,虽然我可以看到它是在与主线程不同的线程中执行的(参见下面的程序输出)。

以下是再现问题的示例:

from PySide.QtCore import QThread, QObject, QCoreApplication, QTimer
import time
import sys

class BackgroundWorker(QObject):
    def __init__(self):
        QObject.__init__(self)

    def __process(self):
        self.process()

    def process(self):
        print 'Background thread:', QThread.currentThread()
        for i in xrange(0, 5):
            print 'In background thread', i
            time.sleep(1)
        self.thread.quit()

    def start(self):
        self.thread = QThread()        
        self.moveToThread(self.thread)
        #self.thread.started.connect(self.process)      # <---- Case 1: This works as expected
        self.thread.started.connect(self.__process)    # <---- Case 2: Main event loop freezes. 
        self.thread.finished.connect(app.quit)
        self.thread.start()

def main_thread():
    print 'Main thread:', QThread.currentThread()
    for i in xrange(0, 5):
        print 'In main thread', i
        time.sleep(1)        

if __name__ == '__main__':
    app = QCoreApplication(sys.argv)
    bw = BackgroundWorker()
    QTimer.singleShot(0, bw.start)
    QTimer.singleShot(100, main_thread)
    app.exec_()

在上面的方法'BackgroundWorker.start'中,有两行用于将线程'started'信号连接到插槽。 “案例1”行按预期工作(公共插槽已连接),但“案例2”行 - 不是(私有插槽已连接)。

案例1的程序输出:

Background thread: <PySide.QtCore.QThread object at 0x02161738>
In background thread 0
Main thread: <PySide.QtCore.QThread object at 0x021616E8>
In main thread 0
In background thread 1
In main thread 1
In background thread 2
In main thread 2
In background thread 3
In main thread 3
In background thread 4
In main thread 4

案例2的程序输出:

Background thread: <PySide.QtCore.QThread object at 0x021916E8>
In background thread 0
In background thread 1
In background thread 2
In background thread 3
In background thread 4
Main thread: <PySide.QtCore.QThread object at 0x02191788>
In main thread 0
In main thread 1
In main thread 2
In main thread 3
In main thread 4

我怀疑这与PySide中的信号/插槽实现有某种关系,但我没有设法找到任何错误报告或与我观察到的行为相关的事情。

PySide版本是1.2.2,python版本是2.6.6

1 个答案:

答案 0 :(得分:0)

当你在python中连接一个QObject的方法时,它会自动注册其meta-Object作为一个插槽。但是,对于以双下划线开头的方法,这不会发生。

您可以在一个简单的示例中看到这一点:

from PySide.QtCore import *

class Test(QObject):

    sig = Signal()

    def __test(self):
        pass

    def test(self):
        pass

t = Test()
print(t.metaObject().methodCount())  # prints 5
t.sig.connect(t.test)
print(t.metaObject().methodCount())  # prints 6
t.sig.connect(t._Test__test)         # name mangling is applied to the __test method
print(t.metaObject().methodCount())  # still prints 6!

这意味着这样的方法被视为正常函数,它连接到一个信号,将始终在主事件线程中运行。如果你真的想要使用以__开头的方法名称,你需要明确地将它装饰成一个插槽,然后它应该工作:

from PySide.QtCore import QThread, QObject, QCoreApplication, QTimer, Slot
import time
import sys

class BackgroundWorker(QObject):
    def __init__(self):
        QObject.__init__(self)

    @Slot()
    def __process(self):
        self.process()

...