我正在尝试在我的 PySide GUI 应用程序中做一件相当常见的事情:我想将一些 CPU 密集型任务委托给后台线程,以便我的 GUI 保持响应,甚至可以在计算进行时显示进度指示器。
这是我正在做的事情(我在 Python 2.7、Linux x86_64 上使用 PySide 1.1.1):
import sys
import time
from PySide.QtGui import QMainWindow, QPushButton, QApplication, QWidget
from PySide.QtCore import QThread, QObject, Signal, Slot
class Worker(QObject):
done_signal = Signal()
def __init__(self, parent = None):
QObject.__init__(self, parent)
@Slot()
def do_stuff(self):
print "[thread %x] computation started" % self.thread().currentThreadId()
for i in range(30):
# time.sleep(0.2)
x = 1000000
y = 100**x
print "[thread %x] computation ended" % self.thread().currentThreadId()
self.done_signal.emit()
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
self.work_thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.work_thread)
self.work_thread.started.connect(self.worker.do_stuff)
self.worker.done_signal.connect(self.work_done)
def initUI(self):
self.btn = QPushButton('Do stuff', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
self.btn.clicked.connect(self.execute_thread)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.show()
def execute_thread(self):
self.btn.setEnabled(False)
self.btn.setText('Waiting...')
self.work_thread.start()
print "[main %x] started" % (self.thread().currentThreadId())
def work_done(self):
self.btn.setText('Do stuff')
self.btn.setEnabled(True)
self.work_thread.exit()
print "[main %x] ended" % (self.thread().currentThreadId())
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
该应用程序显示一个带有按钮的窗口。按下按钮时,我希望它在执行计算时自行禁用。然后,应重新启用该按钮。
相反,当我按下按钮时,整个窗口在计算进行时冻结,然后,当它完成时,我重新获得对应用程序的控制。该按钮似乎从未被禁用。我注意到的一件有趣的事情是,如果我do_stuff()
用一个简单的 time.sleep() 替换 CPU 密集型计算,则程序会按预期运行。
我不完全知道发生了什么,但似乎第二个线程的优先级如此之高,以至于它实际上阻止了 GUI 线程被调度。如果第二个线程进入 BLOCKED 状态(就像它发生在 a 中一样sleep()
),则 GUI 实际上有机会按预期运行和更新界面。我试图改变工作线程的优先级,但它看起来无法在 Linux 上完成。
此外,我尝试打印线程 ID,但我不确定我是否正确执行此操作。如果我是,线程亲和力似乎是正确的。
我还尝试了使用 PyQt 的程序,并且行为完全相同,因此标签和标题。如果我可以使用 PyQt4 而不是 PySide 运行它,我可以将整个应用程序切换到 PyQt4