您需要允许在循环运行时处理事件,以便应用程序可以保持响应。
更重要的是,对于长时间运行的任务,您需要为用户提供一种在循环启动后停止循环的方法。
一种简单的方法是使用计时器启动循环,然后在循环运行时定期调用qApp.processEvents。
这是一个演示脚本:
import sys, time
from PyQt4 import QtGui, QtCore
class ProgressBar(QtGui.QWidget):
def __init__(self, parent=None, total=20):
super(ProgressBar, self).__init__(parent)
self.progressbar = QtGui.QProgressBar()
self.progressbar.setMinimum(1)
self.progressbar.setMaximum(total)
self.button = QtGui.QPushButton('Start')
self.button.clicked.connect(self.handleButton)
main_layout = QtGui.QGridLayout()
main_layout.addWidget(self.button, 0, 0)
main_layout.addWidget(self.progressbar, 0, 1)
self.setLayout(main_layout)
self.setWindowTitle('Progress')
self._active = False
def handleButton(self):
if not self._active:
self._active = True
self.button.setText('Stop')
if self.progressbar.value() == self.progressbar.maximum():
self.progressbar.reset()
QtCore.QTimer.singleShot(0, self.startLoop)
else:
self._active = False
def closeEvent(self, event):
self._active = False
def startLoop(self):
while True:
time.sleep(0.05)
value = self.progressbar.value() + 1
self.progressbar.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or
value >= self.progressbar.maximum()):
break
self.button.setText('Start')
self._active = False
app = QtGui.QApplication(sys.argv)
bar = ProgressBar(total=101)
bar.show()
sys.exit(app.exec_())
更新
假设您使用的是 Python 的 C 实现(即 CPython),则此问题的解决方案完全取决于必须与 GUI 并发运行的任务的性质。更根本的是,它是由具有全局解释器锁 (GIL)的 CPython 确定的。
我不会尝试对 CPython 的 GIL 进行任何解释:相反,我会简单地推荐观看 Dave Beazley 的这个出色的PyCon 视频,然后就这样吧。
通常,当尝试同时运行 GUI 和后台任务时,要问的第一个问题是:任务是 IO-bound 还是 CPU-bound?
如果它是 IO 绑定的(例如访问本地文件系统、从 Internet 下载等),那么解决方案通常非常简单,因为 CPython 总是为 I/O 操作释放 GIL。后台任务可以简单地异步完成,或者由工作线程执行,不需要做任何特别的事情来保持 GUI 响应。
主要困难出现在 CPU 密集型任务上,当有第二个问题要问时:可以将任务分解为一系列小步骤吗?
如果可以,则解决方案是定期向 GUI 线程发送请求以处理其当前的未决事件堆栈。上面的演示脚本是这种技术的粗略示例。更常见的是,该任务将在一个单独的工作线程中执行,该工作线程会在任务的每个步骤完成时发出一个 gui-update 信号。(注意:确保工作线程从不尝试任何与 GUI 相关的操作本身很重要)。
但是如果不能将任务分解成小步骤,那么通常的线程类型的解决方案都不会起作用。无论是否使用线程,GUI 都会冻结直到任务完成。
对于最后一个场景,唯一的解决方案是使用单独的进程,而不是单独的线程——即使用多处理模块。当然,这个解决方案只有在目标系统有多个 CPU 内核可用时才有效。如果只有一个 CPU 内核可以使用,那么基本上没有什么可以帮助的(除了切换到 Python 的不同实现,或完全不同的语言)。