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
中直接调用的,它不拥有任何小部件。
我有一个很长的列表要处理,所以我使用多处理来加快处理速度。现在我想在 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
中直接调用的,它不拥有任何小部件。