若从后台线程调用专用槽,为什么主事件循环会被冻结



我正在使用python和PySide开发GUI应用程序。我需要在一个单独的线程中运行长时间的回溯任务。对于线程,我决定根据"正确"的方法使用QThread,而不从if(请参阅http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/)。所以,为了在后台运行我的长任务,我只需将线程的信号"启动"连接到一个执行长任务的方法。

由于某种原因,我的程序没有按预期运行。我花了几个小时才发现,如果我将插槽名称重命名为"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

当您在python中连接QObject的方法时,它会自动将其元对象注册为槽。但是,以双下划线开头的方法不会发生这种情况。

你可以在一个简单的例子中看到这一点:

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

最新更新