Python3,PyQt5:QProgressBar更新使实际任务非常慢

时间:2016-10-12 16:22:03

标签: python python-3.x pyqt5 signals-slots qprogressbar

我在OSX上使用Python 3.5,PyQt5,我想知道是否有可能在不减慢整个计算工作的情况下更新QProgressBar。 这是我的代码,如果我只做了没有进度条更新的任务,那就太快了!

from PyQt5.QtWidgets import (QWidget, QProgressBar, QPushButton, QApplication)
from jellyfish import levenshtein_distance, jaro_winkler
import sys

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(30, 40, 200, 25)
        self.btn = QPushButton('Start', self)
        self.btn.move(40, 80)
        self.btn.clicked.connect(self.doAction)
        self.setGeometry(300, 300, 280, 170)
        self.show()


    def doAction(self):

        #setup variables
        step = 0
        m = 1000
        n = 500
        step_val = 100 / (m * n)

        #make task
        for i in range(m):
            for j in range(n):
                jaro_winkler(str(i), str(j))

                #show task
                print(i,j)

                #update progressbar
                step += step_val
                self.pbar.setValue(step)
                QApplication.processEvents()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

然后在stackoverflow用户的帮助下,我得到了一个单独的工作线程的提示,并将更新信号连接到GUI。我做了它,现在看起来像下面的代码。它也可以工作并且速度更快,但我无法弄清楚如何将发出的信号连接到GUI。有人能帮帮我吗?非常感谢提前!

from jellyfish import jaro_winkler
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QProgressBar, QMainWindow
import time
import numpy as np

class Main_Window(QMainWindow):

    def __init__(self):
        super(Main_Window,self).__init__()
        self.initUI()

    def initUI(self):
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(30, 40, 200, 25)
        self.btn = QPushButton('Start', self)
        self.btn.move(40, 80)
        self.btn.clicked.connect(MyThread.doAction)
        self.setGeometry(300, 300, 280, 170)
        self.show()

    def updateProgressBar(self, val):
        self.pbar.setValue.connect(val)


class MySignal(QWidget):
    pbar_signal = QtCore.pyqtSignal(int)


class MyThread(QtCore.QThread):
    def __init__(self):
        super().__init__()

    def doAction(self):
        t = time.time()     #for time measurement

        #setup variables
        step = 0
        m = 1000
        n = 500
        pbar_val = 100 / m
        signal_instance = MySignal()

        #make task
        for i in range(m):
            for j in range(n):
                jaro_winkler(str(i), str(j))
            signal_instance.pbar_signal.emit(pbar_val)

        #measuring task time
        print(np.round_(time.time() - t, 3), 'sec elapsed')


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    w = Main_Window()
    sys.exit(app.exec_())

1 个答案:

答案 0 :(得分:0)

减慢代码的速度有三个:

  1. 打印到stdout是非常昂贵的 - 特别是当你这样做了50万次!在我的系统上,评论print(i,j)大约将doAction运行时间缩短一半。
  2. 拨打processEvents 500,000次也是相当昂贵的。评论QApplication.processEvents()会将运行时间减少三分之二。
  3. 评论self.pbar.setValue(step)再次将时间减半。
  4. 希望现在显而易见的是,在一项应该花费不到一秒的任务期间尝试更新gui 500,000次是大规模的过度杀伤!大多数用户的反应时间最多约为200毫秒,因此您只需每100毫秒更新一次gui。

    鉴于此,一个简单的解决方法是将更新移动到外部循环中:

        for i in range(m):
            for j in range(n):
                jaro_winkler(str(i), str(j))
    
                # show task
                # print(i,j)
    
                step += step_val
    
            # update progressbar
            self.pbar.setValue(step)
            QApplication.processEvents()
    

    但是更好的解决方案是将计算移动到单独的工作线程中并让它定期发出自定义信号来更新进度条:

    class Main_Window(QMainWindow):
        ...
        def initUI(self):
            ...
            self.btn.clicked.connect(self.doAction)
            self.thread = MyThread()
            self.thread.pbar_signal.connect(self.pbar.setValue)
    
        def doAction(self):
            if not self.thread.isRunning():
                self.thread.start()    
    
    class MyThread(QtCore.QThread):
        pbar_signal = QtCore.pyqtSignal(int)
    
        def run(self):
            #for time measurement
            t = time.time()
    
            #setup variables
            m = 1000
            n = 500
            progress = step = 100 / m
    
            #make task
            for i in range(m):
                for j in range(n):
                    jaro_winkler(str(i), str(j))
                progress += step
                self.pbar_signal.emit(progress)
    
            #measuring task time
            print(np.round_(time.time() - t, 3), 'sec elapsed')