12

使用以下代码时,我的应用程序会在几秒钟后停止。我所说的摊位是指挂起。我从 Windows 收到一个窗口,提示等待或强制关闭。

我可能会补充说,这只发生在我单击进度条窗口内或单击进度条窗口外部时,它会失去焦点。如果我开始这个例子并且不碰任何东西,它就会像它应该的那样工作。

from PyQt4 import QtCore
from PyQt4 import QtGui


class ProgressBar(QtGui.QWidget):
    def __init__(self, parent=None, total=20):
        super(ProgressBar, self).__init__(parent)
        self.name_line = QtGui.QLineEdit()

        self.progressbar = QtGui.QProgressBar()
        self.progressbar.setMinimum(1)
        self.progressbar.setMaximum(total)

        main_layout = QtGui.QGridLayout()
        main_layout.addWidget(self.progressbar, 0, 0)

        self.setLayout(main_layout)
        self.setWindowTitle("Progress")

    def update_progressbar(self, val):
        self.progressbar.setValue(val)   

像这样使用它:

app = QtGui.QApplication(sys.argv)
bar = ProgressBar(total=101)
bar.show()

for i in range(2,100):
    bar.update_progressbar(i)
    time.sleep(1)

谢谢你的帮助。

4

2 回答 2

13

您需要允许在循环运行时处理事件,以便应用程序可以保持响应。

更重要的是,对于长时间运行的任务,您需要为用户提供一种在循环启动后停止循环的方法。

一种简单的方法是使用计时器启动循环,然后在循环运行时定期调用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 的不同实现,或完全不同的语言)。

于 2012-11-07T17:53:07.070 回答
0

当您进行 gui 编程时,您需要使用某种形式的多线程,您将拥有一个工作线程和一个更新 gui 并响应鼠标和键盘事件的线程。有很多方法可以做到这一点。我建议你看看这个QProgressBar 教程,它有一个关于如何使用 QProgressBar 的出色工作示例。

在本教程中,他们使用 QBasicTimer,这是一种将控制权交还给主线程的方式,以便它可以响应 GUI 事件。通过time.sleep在您的代码中使用,您将阻塞唯一正在运行的线程一秒钟。

于 2012-11-07T13:02:10.340 回答