簡體   English   中英

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 warning that you might have been getting. 這是因為現在display是在適當的線程中調用的(因為它是使用信號調用的),而之前它是在線程t直接調用的,該線程不擁有任何小部件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM