【问题标题】:PyQt5: single QProgressBar with multiprocessing gets stuckPyQt5:具有多处理功能的单个 QProgressBar 卡住了
【发布时间】:2021-11-01 09:23:47
【问题描述】:

我有一个很长的列表要处理,所以我使用多处理来加快处理速度。现在我想在 PyQt5.QtWidgets.QProgressBar 中显示进度。这是代码:

import sys
from PyQt5.QtWidgets import *
import multiprocessing as mp
import threading

targets = list(range(0, 100))


def process(i):
    print("target:", i)
    # do something, for example:
    for c in range(1000):
        for r in range(1000):
            c = c * r + 4


class MainWin(QWidget):

    def __init__(self):
        super(MainWin, self).__init__()
        self.setupUi()

    def setupUi(self):
        self.setFixedSize(500, 90)
        self.layout = QGridLayout()
        self.main_widget = QWidget(self)
        self.progressBar = QProgressBar()
        self.progressBar.setValue(0)

        self.btn = QPushButton('start')

        self.layout.addWidget(self.progressBar, 0, 0, 1, 1)
        self.layout.addWidget(self.btn, 1, 0, 1, 1)
        self.setLayout(self.layout)

        self.btn.clicked.connect(self.run)

    def display(self, args):
        self.progressBar.setValue(self.progressBar.value() + 1)
        print("process bar:", self.progressBar.value())
        # QApplication.processEvents()  # I've tried this function and it has no effect

    def run(self):
        def func(results):
            pool = mp.Pool()
            for t in targets:
                pool.apply_async(process, (t,), callback=self.display)
                results.append(t)
            pool.close()
            pool.join()

        results = []
        t = threading.Thread(target=func, args=(results,))
        t.start()
        # everything is fine without t.join(), but the progress bar always gets stuck when t.join() is called
        # t.join()
        # pass  # do something with the results


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_win = MainWin()
    main_win.show()
    sys.exit(app.exec_())

没有调用“t.join()”一切都很好。但是要获得完整的“结果”,我必须等待线程结束,在这种情况下 processBar 总是卡在 40% 左右。

如何解决这个问题?

【问题讨论】:

    标签: python pyqt5 python-multiprocessing qprogressbar


    【解决方案1】:

    PyQt5(和一般的Qt)需要在主线程中不断运行事件循环才能发挥作用:重绘GUI,对用户输入做出反应等。如果你调用t.join(),主线程就会卡在里面run 方法,阻止线程和所有 GUI 更新,包括重绘进度条。为了正常运行,代码应该尽快退出run,所以t.join()不是一个好的解决方案。

    其中一种处理方法是使用 Qt 信号。首先,等待它们不会阻塞主循环,因此 GUI 保持响应。其次,它们在主线程中执行,因此从非主线程访问 Qt 小部件没有问题(这通常是个坏主意)。以下是我建议重写代码的方式:

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import pyqtSignal, pyqtSlot
    import multiprocessing as mp
    import threading
    
    targets = list(range(0, 100))
    
    
    def process(i):
        print("target:", i)
        # do something, for example:
        for c in range(1000):
            for r in range(1000):
                c = c * r + 4
    
    
    class MainWin(QWidget):
    
        def __init__(self):
            super(MainWin, self).__init__()
            self.setupUi()
            self.done = False
    
        def setupUi(self):
            self.setFixedSize(500, 90)
            self.layout = QGridLayout()
            self.main_widget = QWidget(self)
            self.progressBar = QProgressBar(self.main_widget)
            self.progressBar.setValue(0)
    
            self.btn = QPushButton('start',self.main_widget)
    
            self.layout.addWidget(self.progressBar, 0, 0, 1, 1)
            self.layout.addWidget(self.btn, 1, 0, 1, 1)
            self.setLayout(self.layout)
    
            self.btn.clicked.connect(self.run)
            self.single_done.connect(self.display)
            self.all_done.connect(self.process_results)
    
        single_done = pyqtSignal()
        @pyqtSlot()
        def display(self):
            self.progressBar.setValue(self.progressBar.value() + 1)
            print("process bar:", self.progressBar.value())
        
        all_done = pyqtSignal()
        @pyqtSlot()
        def process_results(self):
            print("Processing results")
            pass  # do something with the results
    
        def run(self):
            def func(results):
                pool = mp.Pool()
                for t in targets:
                    pool.apply_async(process, (t,), callback=lambda *args: self.single_done.emit())
                    results.append(t)
                pool.close()
                pool.join()
                self.all_done.emit()
    
            results = []
            t = threading.Thread(target=func, args=(results,))
            t.start()
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main_win = MainWin()
        main_win.show()
        sys.exit(app.exec_())
    

    我添加了两个信号:single_done,每次完成单个目标执行时都会发出;all_done,在所有处理完成时发出。在setupUi 的末尾,它们连接到用于更新进度条和处理结果的相应方法。 run 不再停留并立即退出,结果的处理现在在 process_result 方法中完成,该方法在完成时调用。

    顺便说一句,使用信号报告中间结果也可以消除您可能已经收到的QObject::setParent: Cannot set parent, new parent is in a different thread 警告。这是因为现在display 是在正确的线程中调用的(因为它是使用信号调用的),而之前它是在线程t 中直接调用的,它不拥有任何小部件。

    【讨论】:

    • 太棒了!现在它完美运行了!
    猜你喜欢
    • 2016-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 2019-05-09
    相关资源
    最近更新 更多