多处理GUI模式以应对“无响应”阻止
创建多处理/ GUI编码系统的最佳方法是什么?
我想为互联网社区创建一个场所,以找到有关如何multiprocessing
在python中使用该模块的示例。
我已经multiprocessing
在互联网上看到了几个简单的全局函数处理过程的小例子,这些过程在一个主模块中被调用,但是我发现这很少会轻易地转化为任何人实际上对GUI所做的任何事情。我认为许多程序将具有在单独的过程中作为对象方法使用的功能(可能是其他对象的集合等),也许单个GUI元素将具有关联的对象,需要调用此函数过程等
例如,我有一个比较复杂的程序,我有由于我缺乏了解在得到它一个负责任的GUI,我认为是问题multiprocessing
,并与线程QThread
。但是,我确实知道,下面给出的示例将至少以我希望的方式(由于能够执行print
语句)在进程之间传递信息,但是我的GUI仍然处于锁定状态。有谁知道这是什么原因造成的,是否仍然是我缺乏对多线程/多处理体系结构的了解的探究?
这是我正在做的一个小的伪代码示例:
class Worker:
...
def processing(self, queue):
# put stuff into queue in a loop
# This thread gets data from Worker
class Worker_thread(QThread):
def __init__(self):
...
# make process with Worker inside
def start_processing(self):
# continuously get data from Worker
# send data to Tab object with signals/slots
class Tab(QTabWidget):
# spawn a thread separate from main GUI thread
# update GUI using slot
def update_GUI()
该代码是完全可编译的示例,体现了我程序的总体结构:
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import numpy as np
import sys
import time
# This object can hold several properties which will be used for the processing
# and will be run in the background, while it updates a thread with all of it's progress
class Worker:
def __init__(self, some_var):
self.some_var = some_var
self.iteration = 0
def some_complex_processing(self, queue):
for i in range(0,5000):
self.iteration += 1
queue.put(self.iteration)
queue.put('done with processing')
# This Woker_thread is a thread which will spawn a separate process (Worker).
# This separate is needed in order to separate the data retrieval
# from the main GUI thread, which should only quickly update when needed
class Worker_thread(QtCore.QThread):
# signals and slots are used to communicate back to the main GUI thread
update_signal = QtCore.pyqtSignal(int)
done_signal = QtCore.pyqtSignal()
def __init__(self, parent, worker):
QtCore.QThread.__init__(self, parent)
self.queue = mp.Queue()
self.worker = worker
self.parent = parent
self.process = mp.Process(target=self.worker.some_complex_processing, args=(self.queue,))
# When the process button is pressed, this function will start getting data from Worker
# this data is then retrieved by the queue and pushed through a signal
# to Tab.update_GUI
@QtCore.pyqtSlot()
def start_computation(self):
self.process.start()
while(True):
try:
message = self.queue.get()
self.update_signal.emit(message)
except EOFError:
pass
if message == 'done with processing':
self.done_signal.emit()
break
#self.parent.update_GUI(message)
self.process.join()
return
# Each tab will start it's own thread, which will spawn a process
class Tab(QtGui.QTabWidget):
start_comp = QtCore.pyqtSignal()
def __init__(self, parent, this_worker):
self.parent = parent
self.this_worker = this_worker
QtGui.QTabWidget.__init__(self, parent)
self.treeWidget = QtGui.QTreeWidget(self)
self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"])
self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"])
self.thread = Worker_thread(parent=self, worker=self.this_worker)
self.thread.update_signal.connect(self.update_GUI)
self.thread.done_signal.connect(self.thread.quit)
self.start_comp.connect(self.thread.start_computation)
self.thread.start()
###############################
# Here is what should update the GUI at every iteration of Worker.some_complex_processing()
# The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated.
@QtCore.pyqtSlot(int)
def update_GUI(self, iteration):
self.step.setText(0, str(iteration))
#time.sleep(0.1)
print iteration
def start_signal_emit(self):
self.start_comp.emit()
# GUI stuff
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self)
self.tab_list = []
self.setTabShape(QtGui.QTabWidget.Rounded)
self.centralwidget = QtGui.QWidget(self)
self.top_level_layout = QtGui.QGridLayout(self.centralwidget)
self.tabWidget = QtGui.QTabWidget(self.centralwidget)
self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25)
process_button = QtGui.QPushButton("Process")
self.top_level_layout.addWidget(process_button, 0, 1)
QtCore.QObject.connect(process_button, QtCore.SIGNAL("clicked()"), self.process)
self.setCentralWidget(self.centralwidget)
self.centralwidget.setLayout(self.top_level_layout)
# Make Tabs in loop from button
for i in range(0,10):
name = 'tab' + str(i)
self.tab_list.append(Tab(self.tabWidget, Worker(name)))
self.tabWidget.addTab(self.tab_list[-1], name)
# Do the processing
def process(self):
for tab in self.tab_list:
tab.start_signal_emit()
return
if __name__ == "__main__":
app = QtGui.QApplication([])
win = MainWindow()
win.show()
sys.exit(app.exec_())
更多信息: 我正在编写一个程序,希望从中产生多个过程,并使它们在整个处理过程中不断显示其进度。我希望对程序进行多进程处理,以使程序获得最佳速度。
目前,我正在尝试使用线程来生成进程,并使用信号和插槽来更新GUI,同时队列不断检索数据。看来,queues
,signals
,和slots
使用时的工作print
报表,但不能更新GUI。如果有人对我应该如何构造它以使程序更易于管理有其他建议,我想学习。
编辑 :我已经进行了敏琳提出的调整,加上做Worker
了一个QObject
这样的moveToThread()
工作。
这是我目前拥有的新代码:
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import numpy as np
import sys
import time
class Worker(QtCore.QObject):
update_signal = QtCore.pyqtSignal(int)
done_signal = QtCore.pyqtSignal()
def __init__(self, some_var):
QtCore.QObject.__init__(self, parent=None)
self.some_var = some_var
self.iteration = 0
self.queue = mp.Queue()
self.process = mp.Process(target=self.some_complex_processing, args=(self.queue,))
def some_complex_processing(self, queue):
for i in range(0,5000):
self.iteration += 1
queue.put(self.iteration)
queue.put('done with processing')
@QtCore.pyqtSlot()
def start_computation(self):
self.process.start()
while(True):
try:
message = self.queue.get()
self.update_signal.emit(message)
except EOFError:
pass
if message == 'done with processing':
self.done_signal.emit()
break
self.process.join()
return
class Tab(QtGui.QTabWidget):
start_comp = QtCore.pyqtSignal()
def __init__(self, parent, this_worker):
self.parent = parent
self.this_worker = this_worker
QtGui.QTabWidget.__init__(self, parent)
self.treeWidget = QtGui.QTreeWidget(self)
self.properties = QtGui.QTreeWidgetItem(self.treeWidget, ["Properties"])
self.step = QtGui.QTreeWidgetItem(self.properties, ["Iteration #"])
# Use QThread is enough
self.thread = QtCore.QThread();
# Change the thread affinity of worker to self.thread.
self.this_worker.moveToThread(self.thread);
self.this_worker.update_signal.connect(self.update_GUI)
self.this_worker.done_signal.connect(self.thread.quit)
self.start_comp.connect(self.this_worker.start_computation)
self.thread.start()
###############################
# Here is what should update the GUI at every iteration of Worker.some_complex_processing()
# The message appears to be getting sent, due to seeing the print statement in the console, but the GUI is not updated.
@QtCore.pyqtSlot(int)
def update_GUI(self, iteration):
self.step.setText(0, str(iteration))
#time.sleep(0.1)
print iteration
def start_signal_emit(self):
self.start_comp.emit()
# GUI stuff
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self)
self.tab_list = []
self.setTabShape(QtGui.QTabWidget.Rounded)
self.centralwidget = QtGui.QWidget(self)
self.top_level_layout = QtGui.QGridLayout(self.centralwidget)
self.tabWidget = QtGui.QTabWidget(self.centralwidget)
self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25)
process_button = QtGui.QPushButton("Process")
self.top_level_layout.addWidget(process_button, 0, 1)
QtCore.QObject.connect(process_button, QtCore.SIGNAL("clicked()"), self.process)
self.setCentralWidget(self.centralwidget)
self.centralwidget.setLayout(self.top_level_layout)
# Make Tabs in loop from button
for i in range(0,10):
name = 'tab' + str(i)
self.tab_list.append(Tab(self.tabWidget, Worker(name)))
self.tabWidget.addTab(self.tab_list[-1], name)
# Do the processing
def process(self):
for tab in self.tab_list:
tab.start_signal_emit()
return
if __name__ == "__main__":
app = QtGui.QApplication([])
win = MainWindow()
win.show()
sys.exit(app.exec_())
感谢您提供所有答案,我很感谢每个人在描述他们认为是解决方案的想法时所涉及的细节,但是不幸的是,我尚未能够执行对它们所属的对象进行这些类型的处理的过程同时在GUI上显示对象的属性。
但是,我从这篇文章中学到了很多,这使我意识到我现在拥有的线程版本正在挂起GUI,因为GUI更新功能太大并且需要太多处理。
因此,我将这种QTimer()
方法用于我的多线程版本,并且性能要好得多!我建议任何面临类似问题的人至少尝试类似的尝试。
我没有意识到解决GUI更新问题的方法,现在它只是针对我所面临问题的伪或临时解决方案。
-
GUI应用程序非常适合测试内容,因为它很容易产生新任务并可视化正在发生的事情,所以我写了一个小示例应用程序(屏幕截图,下面的代码),因为我确实想自己学习。
最初,我采用了与您类似的方法,尝试实现“消费者/生产者”模式,并且我在后台进程中苦苦挣扎,不断循环以等待新工作,并自己来回进行通信。然后我发现了池接口,然后我可以用几行代码替换所有这些令人讨厌的代码。您需要的只是一个池和一些回调:
#!/usr/bin/env python3 import multiprocessing, time, random, sys from PySide.QtCore import * # equivalent: from PyQt4.QtCore import * from PySide.QtGui import * # equivalent: from PyQt4.QtGui import * def compute(num): print("worker() started at %d" % num) random_number = random.randint(1, 6) if random_number in (2, 4, 6): raise Exception('Random Exception in _%d' % num) time.sleep(random_number) return num class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.toolBar = self.addToolBar("Toolbar") self.toolBar.addAction(QAction('Add Task', self, triggered=self.addTask)) self.list = QListWidget() self.setCentralWidget(self.list) # Pool of Background Processes self.pool = multiprocessing.Pool(processes=4) def addTask(self): num_row = self.list.count() self.pool.apply_async(func=compute, args=(num_row,), callback=self.receiveResult, error_callback=self.receiveException) item = QListWidgetItem("item %d" % num_row) item.setForeground(Qt.gray) self.list.addItem(item) def receiveResult(self, result): assert isinstance(result, int) print("end_work(), where result is %s" % result) self.list.item(result).setForeground(Qt.darkGreen) def receiveException(self, exception): error = str(exception) _pos = error.find('_') + 1 num_row = int(error[_pos:]) item = self.list.item(num_row) item.setForeground(Qt.darkRed) item.setText(item.text() + ' Retry...') self.pool.apply_async(func=compute, args=(num_row,), callback=self.receiveResult, error_callback=self.receiveException) if __name__ == '__main__': app = QApplication(sys.argv) main_window = MainWindow() main_window.show() sys.exit(app.exec_())
编辑:我做了另一个示例,使用QTimer而不是回调,定期检查队列中的条目,更新QProgressBar:
#!/usr/bin/env python3 import multiprocessing, multiprocessing.pool, time, random, sys from PySide.QtCore import * from PySide.QtGui import * def compute(num_row): print("worker started at %d" % num_row) random_number = random.randint(1, 10) for second in range(random_number): progress = float(second) / float(random_number) * 100 compute.queue.put((num_row, progress,)) time.sleep(1) compute.queue.put((num_row, 100)) def pool_init(queue): # see http://stackoverflow.com/a/3843313/852994 compute.queue = queue class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.toolBar = self.addToolBar("Toolbar") self.toolBar.addAction(QAction('Add Task', self, triggered=self.addTask)) self.table = QTableWidget() self.table.verticalHeader().hide() self.table.setColumnCount(2) self.setCentralWidget(self.table) # Pool of Background Processes self.queue = multiprocessing.Queue() self.pool = multiprocessing.Pool(processes=4, initializer=pool_init, initargs=(self.queue,)) # Check for progress periodically self.timer = QTimer() self.timer.timeout.connect(self.updateProgress) self.timer.start(2000) def addTask(self): num_row = self.table.rowCount() self.pool.apply_async(func=compute, args=(num_row,)) label = QLabel("Queued") bar = QProgressBar() bar.setValue(0) self.table.setRowCount(num_row + 1) self.table.setCellWidget(num_row, 0, label) self.table.setCellWidget(num_row, 1, bar) def updateProgress(self): if self.queue.empty(): return num_row, progress = self.queue.get() # unpack print("received progress of %s at %s" % (progress, num_row)) label = self.table.cellWidget(num_row, 0) bar = self.table.cellWidget(num_row, 1) bar.setValue(progress) if progress == 100: label.setText('Finished') elif label.text() == 'Queued': label.setText('Downloading') self.updateProgress() # recursion if __name__ == '__main__': app = QApplication(sys.argv) main_window = MainWindow() main_window.show() sys.exit(app.exec_())