【问题标题】:PyQt ProgressBarPyQt 进度条
【发布时间】:2012-10-27 12:31:21
【问题描述】:

使用以下代码时,我的应用程序会在几秒钟后停止。 我所说的摊位是指挂起。我从 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)

感谢您的帮助。

【问题讨论】:

    标签: python multithreading qt pyqt qprogressbar


    【解决方案1】:

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

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

    执行此操作的一种简单方法是使用计时器启动循环,然后在循环运行时定期调用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 并发运行的任务的性质.更根本的是,它是由 CPython 的 Global Interpreter Lock (GIL) 决定的。

    我不会尝试对 CPython 的 GIL 进行任何解释:相反,我只是建议您观看 Dave Beazley 的精彩 PyCon video,然后就这样吧。


    通常,当尝试同时运行 GUI 和后台任务时,首先要问的问题是:任务是受 IO 限制还是受 CPU 限制?

    如果它受 IO 限制(例如访问本地文件系统、从 Internet 下载等),那么解决方案通常非常简单,因为 CPython 总是为 I/O 操作释放 GIL。后台任务可以简单地由asynchronously 完成,或由worker thread 执行,不需要做任何特别的事情来保持GUI 响应。

    主要困难出现在 CPU 密集型任务上,当有第二个问题要问时:可以将任务分解为一系列小步骤吗?

    如果可以,则解决方案是定期向 GUI 线程发送请求以处理其当前的未决事件堆栈。上面的演示脚本是这种技术的一个粗略示例。更常见的是,该任务将在一个单独的工作线程中执行,该工作线程将在任务的每个步骤完成时发出一个 gui-update 信号。 (注意:确保工作线程从不尝试任何与 GUI 相关的操作本身很重要)。

    但是如果任务不能分解成小步骤,那么通常的线程类型的解决方案都不起作用。无论是否使用线程,GUI 都会冻结,直到任务完成。

    对于最后一种情况,唯一的解决方案是使用单独的进程,而不是单独的线程——即使用multiprocessing 模块。当然,这个解决方案只有在目标系统有多个 CPU 内核可用时才有效。如果只有一个 CPU 内核可供使用,那么基本上没有什么可以帮助的(除了切换到 Python 的不同实现,或者完全切换到不同的语言)。

    【讨论】:

    • 如果我运行它,GUI 窗口也会停止并出现“无响应,强制关闭”错误。但是,如果我等到所有任务都完成后,正常的应用程序会继续。
    • @Tuim。我的脚本只是一个基于您问题中的代码的简单演示。这不是一个适用于所有情况的通用解决方案。您需要通过正确解释您正在尝试做什么来更新您的问题。你提到的这些“任务”是什么?它们是受 CPU 限制还是受 IO 限制?执行任务的应用程序是您自己编写的,因此可以修改吗?它是用什么语言写的?等等等等。
    • 这些任务是安装,例如解压缩 zip 文件,安装 msi/deb 包之类的东西。但这与本案的关系不大。该应用程序也是用 Python 编写的,并且完全可定制。我也不期待一个可以复制粘贴的答案!我期待一个正确方向的提示,而你的提示对我来说似乎不是正确的方向,我已经尝试过了。没有冒犯。
    • @Tuim。相反:任务的性质可能是唯一与您的案例相关的事情。请查看我的答案的更新。
    • 感谢您的广泛回答。
    【解决方案2】:

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

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

    【讨论】:

    • 我知道这一点。但他们不再像我在示例中那样使用线程。
    • @Tuim:是的,不要使用time.sleep()
    • @Tuim 我更新了答案以进一步解释为什么您的代码和教程没有做同样的事情。
    • 我认为在这里提到线程只会让事情变得混乱。不涉及线程,它只是将控制权返回给 Qt 事件循环,以便应用程序可以保持响应。将工作转移到单独的线程是实现这一目标的一种策略,但绝不是必需的。
    • 也许我需要添加更多上下文。我有一个控制台应用程序,它以串行方式执行一组任务。我只是想为此显示一个进度条,因为 QT 是这里首选的 GUI 库,所以我一定会使用它。除主线程外,该应用程序没有任何线程模型。所以我只需要一个 GUI 框架(是否在新线程中)来显示进度条并在任务完成时更新此条。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-26
    • 2017-02-18
    • 2017-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多