PyQt5:具有多处理功能的单个 QProgressBar 卡住了
Posted
技术标签:
【中文标题】PyQt5:具有多处理功能的单个 QProgressBar 卡住了【英文标题】:PyQt5: single QProgressBar with multiprocessing gets stuck 【发布时间】: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% 左右。
如何解决这个问题?
【问题讨论】:
【参考方案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
中直接调用的,它不拥有任何小部件。
【讨论】:
太棒了!现在它完美运行了!以上是关于PyQt5:具有多处理功能的单个 QProgressBar 卡住了的主要内容,如果未能解决你的问题,请参考以下文章
Pyqt5进度条QProgressBar的使用/多线程更新/按钮美化/图片编码/开机自启动