[英]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.