简体   繁体   English

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

[英]PyQt5: single QProgressBar with multiprocessing gets stuck

I have a very long list to deal with, so I use multiprocessing to speed up the process.我有一个很长的列表要处理,所以我使用多处理来加速这个过程。 Now I want to show the progress in a PyQt5.QtWidgets.QProgressBar.现在我想在 PyQt5.QtWidgets.QProgressBar 中显示进度。 This is the code:这是代码:

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_())

Everything is fine without the "t.join()" called.没有调用“t.join()”,一切都很好。 But to gain a complete "results", I have to wait for the end of the thread, in which condition the processBar always gets stuck at around 40%.但是要获得完整的“结果”,我必须等待线程结束,在这种情况下 processBar 总是卡在 40% 左右。

How to fix this?如何解决这个问题?

PyQt5 (and Qt in general) needs to continuously run the event loop in the main thread in order to function: redraw the GUI, react to user inputs, etc. If you call t.join() , the main thread is stuck inside the run method, blocking the thread and all the GUI updates, include redrawing of the progress bar. PyQt5(以及一般的 Qt)需要在主线程中持续运行事件循环才能发挥作用:重绘 GUI、对用户输入做出反应等。如果您调用t.join() ,主线程会卡在run方法,阻塞线程和所有 GUI 更新,包括重绘进度条。 In order to function properly, the code should exit run as soon as possible, so t.join() is not a good solution.为了正常运行,代码应该尽快退出run ,所以t.join()不是一个好的解决方案。

One of the ways to deal with that is by using Qt signals.解决这个问题的方法之一是使用 Qt 信号。 First, waiting for them dos not block the main loop, so the GUI remains responsive.首先,等待它们不会阻塞主循环,因此 GUI 保持响应。 Second, they are executed in the main thread, so there is no problem with accessing Qt widgets from a non-main thread (which is, generally, a bad idea).其次,它们在主线程中执行,因此从非主线程访问 Qt 小部件没有问题(这通常是一个坏主意)。 Here's how I would suggest rewriting the code:以下是我建议重写代码的方式:

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_())

I've added two signals: single_done , which is emitted every time a single target execution is done, and all_done , which is emitted when all the processing is done.我添加了两个信号: single_done ,它在每次单个目标执行完成时发出, all_done ,它在所有处理完成时发出。 In the end of setupUi they are connected to the corresponding methods for updating the progress bar and for processing the results.setupUi的末尾,它们连接到更新进度条和处理结果的相应方法。 run no longer sticks around and exits immediately, and the processing of the results is now done in process_result method, which is called upon completion. run不再停留并立即退出,并且结果的处理现在在process_result方法中完成,该方法在完成时调用。

Incidentally, using signals to report intermediate results also gets rid of QObject::setParent: Cannot set parent, new parent is in a different thread warning which you might have been getting.顺便说一句,使用信号报告中间结果也摆脱了QObject::setParent: Cannot set parent, new parent is in a different thread warning that you might have been getting. This is because now display is called in the proper thread (since it's invoked using signals), whereas before it was called directly in the thread t , which does not own any of the widgets.这是因为现在display是在适当的线程中调用的(因为它是使用信号调用的),而之前它是在线程t直接调用的,该线程不拥有任何小部件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM