PyQt5:具有多处理功能的单个 QProgressBar 卡住了

PyQt5: single QProgressBar with multiprocessing gets stuck

我有一个很长的列表要处理,所以我使用多处理来加快处理速度。现在我想在 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% 左右。

如何解决这个问题?

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 中直接调用的,它不拥有任何小部件。