如何在不使用多线程的情况下显示进度

2024-03-29 00:25:21 发布

您现在位置:Python中文网/ 问答频道 /正文

假设我有一个PyQt程序,它遍历给定的目录,查找*JPEG图像,并在每次找到图像时进行一些处理。根据所选目录的大小,这可能需要几秒钟到几分钟的时间。在

我想让我的用户更新状态-最好是像“x文件处理出y文件”。否则,通过设置progressbar.setRange(0,0)来运行一个简单的脉冲进度条也可以。在

据我所知,为了防止我的GUI冻结,我需要一个单独的线程来处理图像,以及每间隔更新GUI的原始线程。在

但是我想知道有没有什么方法可以让我在同一个线程中同时完成这两个任务?在


Tags: 文件用户图像程序目录状态时间gui
3条回答

如果没有多线程,我无法实现您需要的东西,这是不可能的,因为gui只能在主线程中更新。下面是一个算法,我是如何做到这一点与多线程。在

假设您的应用程序正在处理图像。然后是以下线程:

  1. 主线程(由GUI/QApplication派生的块类.exec())
  2. 例如,具有1秒间隔的计时器更新变量并调用GUI线程中的插槽,后者更新用户界面中的变量。在
  3. 在你的电脑上处理图像的线程

    def process(self):
        self._status = "processing image 1"
        ....
    
    def _update(self):
        self.status_label.setText(self._status)
    
    def start_processing(self, image_path):
        # create thread for process and run it
        # create thread for updating by using QtCore.QTimer()
        # connect qtimer triggered signal to and `self._update()` slot
        # connect image processing thread (use connect signal to any slot, in this example I'll stop timer after processing thread finishes)
        @pyqtSlot()
        def _stop_timer():
            self._qtimer.stop()
            self._qtimer = None
        _update_thread.finished.connect(_stop_timer)
    

在pyqt5中,可以从一个嵌套线程(第一级)分配pyqtvariable。因此,您可以使用setter和getter使变量成为pyqtvariable,并在setter中更新gui,或者思考如何自己完成这项工作。在

是的,您可以很容易地使用processEvents来实现这一点,这正是为此目的而提供的。在

我用这个技术实现了一个简单的“在文件中查找”对话框。您需要做的就是启动一个函数,该函数用single-shot timer处理文件,然后在循环中周期性地调用processEvents。这应该足够好,以更新计数器处理的文件数,也允许用户取消进程,如果必要的话。在

唯一真正的问题是决定调用processEvents的频率。您调用它的频率越高,GUI的响应就越快—但这是以大大减慢文件处理速度为代价的。所以你可能需要做一点实验,以便找到一个可以接受的折衷方案。在

更新

下面是一个简单的演示,演示如何构造代码:

import sys, time
from PyQt5 import QtWidgets, QtCore

class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.button = QtWidgets.QPushButton('Start')
        self.progress = QtWidgets.QLabel('0')
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.button)
        layout.addWidget(self.progress)
        self.button.clicked.connect(self.test)
        self._stop = False
        self._stopped = True

    def test(self):
        if self._stopped:
            self._stop = False
            self.progress.setText('0')
            self.button.setText('Stop')
            QtCore.QTimer.singleShot(1, self.process)
        else:
            self._stop = True

    def process(self):
        self._stopped = False
        for index in range(1, 1000):
            time.sleep(0.01)
            self.progress.setText(str(index))
            if not index % 20:
                QtWidgets.qApp.processEvents(
                    QtCore.QEventLoop.AllEvents, 50)
            if self._stop:
                break
        self._stopped = True
        self.button.setText('Start')

if __name__ == "__main__":

    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

您只需使用pythonthreading模块,并在线程例程中发出一个信号。在

这是一个有效的例子

from PyQt4 import QtGui, QtCore
import threading
import time

class MyWidget(QtGui.QWidget):

    valueChanged = QtCore.pyqtSignal(int)

    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)
        self.computeButton = QtGui.QPushButton("Compute", self)
        self.progressBar = QtGui.QProgressBar()
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.computeButton)
        layout.addWidget(self.progressBar)
        self.computeButton.clicked.connect(self.compute)
        self.valueChanged.connect(self.progressBar.setValue)   

    def compute(self):
        nbFiles = 10
        self.progressBar.setRange(0, nbFiles)

        def inner(): 
            for i in range(1, nbFiles+1):
                time.sleep(0.5)  # Process Image
                self.valueChanged.emit(i)  # Notify progress 

        self.thread = threading.Thread(target = inner)
        self.thread.start()


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    widget = MyWidget()
    widget.show()
    sys.exit(app.exec_())

相关问题 更多 >