Python GUI响应与多个进程不一致
我有一个脚本,它允许同时执行多个进程。除了并行运行外,这些进程还实时向GUI报告其属性,以允许用户跟踪进程的进度。此外,我相信我已经设置了系统,以便允许用户在任何时间点停止进程,更改该进程的属性,然后使用新属性从原来的位置恢复进程 但是,GUI似乎仍然对进程的启动/停止没有很好的响应,并且没有保留对其提供的属性的更改 我收到了一些关于这个脚本的帮助,但是这个问题似乎不适合那个论坛 这是我目前拥有的代码,我希望对其进行更改,以实现我上面列出的所有目标:Python GUI响应与多个进程不一致,python,user-interface,parallel-processing,multiprocessing,Python,User Interface,Parallel Processing,Multiprocessing,我有一个脚本,它允许同时执行多个进程。除了并行运行外,这些进程还实时向GUI报告其属性,以允许用户跟踪进程的进度。此外,我相信我已经设置了系统,以便允许用户在任何时间点停止进程,更改该进程的属性,然后使用新属性从原来的位置恢复进程 但是,GUI似乎仍然对进程的启动/停止没有很好的响应,并且没有保留对其提供的属性的更改 我收到了一些关于这个脚本的帮助,但是这个问题似乎不适合那个论坛 这是我目前拥有的代码,我希望对其进行更改,以实现我上面列出的所有目标: from PyQt4 import QtCo
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import sys
import time
def make_data():
data = {'num': 3, 'num2':4}
return data
num = 1000000
def some_complex_processing(data, can_run):
for i in range(data['num'], data['num'] + num):
can_run.wait()
data['num'] = i
data['num2'] = i+100
time.sleep(0.1)
class Tab(QtGui.QTabWidget):
def __init__(self, parent, data_init):
QtGui.QTabWidget.__init__(self, parent)
self.top_level_layout = QtGui.QGridLayout(self)
self.frame = QtGui.QFrame(self)
self.top_level_layout.addWidget(self.frame)
self.step_label = dict()
self.step_stacked = dict()
self.step_text = dict()
self.step_input = dict()
for wdgts in data_init.keys():
self.step_label[wdgts] = QtGui.QLabel(str(wdgts))
self.step_stacked[wdgts] = QtGui.QStackedWidget()
self.step_text[wdgts] = QtGui.QLabel(str(data_init[wdgts]))
self.step_input[wdgts] = QtGui.QLineEdit()
self.step_stacked[wdgts].addWidget(self.step_text[wdgts])
self.step_stacked[wdgts].addWidget(self.step_input[wdgts])
self.top_level_layout.addWidget(self.step_stacked[wdgts])
self.top_level_layout.addWidget(self.step_label[wdgts])
self.process_button = QtGui.QPushButton("Process")
self.top_level_layout.addWidget(self.process_button, 1, 1)
self.process_button.clicked.connect(self.start_process)
self.manager = mp.Manager()
self.data = self.manager.dict(make_data())
self.Event = self.manager.Event()
self.process = mp.Process(target=some_complex_processing, args=(self.data,self.Event,))
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_GUI)
def update_GUI(self):
try:
for wdgt in self.data.keys():
self.step_label[wdgt].setText(str(wdgt))
self.step_text[wdgt].setText(str(self.data[wdgt]))
self.step_input[wdgt].setText(str(self.data[wdgt]))
except EOFError:
QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
self.timer.stop()
self.process.join()
return
def start_process(self):
self.Event.set()
for wdgt in self.step_stacked.keys():
self.step_stacked[wdgt].setCurrentWidget(self.step_text[wdgt])
self.process.start()
self.timer.start()
self.process_button.setText('Stop Processing - (manually adjust data)')
QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
return
def stop_process(self):
self.Event.clear()
for wdgt in self.step_stacked.keys():
self.step_stacked[wdgt].setCurrentWidget(self.step_input[wdgt])
self.timer.stop()
self.process_button.setText('Start Processing Again Using Data')
QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
return
# GUI stuff
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self)
screen_height = app.desktop().screenGeometry().height()
screen_width = app.desktop().screenGeometry().width()
self.resize(int(screen_width*0.2), int(screen_height*0.2))
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)
self.setCentralWidget(self.centralwidget)
self.centralwidget.setLayout(self.top_level_layout)
self.process_all__button = QtGui.QPushButton("Start All Processes")
self.top_level_layout.addWidget(self.process_all__button, 0, 0)
QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
# Make Tabs in loop from button
for i in range(0,10):
super_cool_data = make_data()
name = 'tab ' + str(i)
self.tab_list.append(Tab(self.tabWidget, super_cool_data))
self.tabWidget.addTab(self.tab_list[-1], name)
def start_all_process(self):
self.process_all__button.setText('Stop All Processing')
QtCore.QObject.disconnect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.stop_all_process)
for i in self.tab_list:
i.start_process()
def stop_all_process(self):
self.process_all__button.setText('Start All Processing')
QtCore.QObject.disconnect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.stop_all_process)
QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
for i in self.tab_list:
i.stop_process()
if __name__ == "__main__":
app = QtGui.QApplication([])
win = MainWindow()
win.show()
sys.exit(app.exec_())
更新:
我对守则作了一些修改,以反映评注中提出的建议。现在,some_complex_processing
函数中的循环将不引用循环迭代编号,而是引用存储在其中的数据,这允许用户查看他们所做的更改
此外,我添加了一个时间间隔来更新GUI,这样它现在就不会阻塞
然而,即使我添加了一个process.daemon=True
,仍然存在保留的僵尸进程的问题。另外,QtCore.QObject.disconnect
操作似乎不起作用,正如我包含的几个打印语句的输出所示。按钮似乎从未断开连接
以下是新代码:
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import sys
import time
def make_data():
data = {'num': 3, 'num2': 4}
return data
num = 10**8
def some_complex_processing(data, can_run):
for i in range(data['num'], data['num'] + num):
can_run.wait()
data['num'] += 1
data['num2'] += 100
class Tab(QtGui.QTabWidget):
def __init__(self, parent, data_init):
QtGui.QTabWidget.__init__(self, parent)
self.top_level_layout = QtGui.QGridLayout(self)
self.frame = QtGui.QFrame(self)
self.top_level_layout.addWidget(self.frame)
self.step_label = dict()
self.step_stacked = dict()
self.step_text = dict()
self.step_input = dict()
for wdgts in data_init.keys():
self.step_label[wdgts] = QtGui.QLabel(str(wdgts))
self.step_stacked[wdgts] = QtGui.QStackedWidget()
self.step_text[wdgts] = QtGui.QLabel(str(data_init[wdgts]))
self.step_input[wdgts] = QtGui.QLineEdit()
self.step_stacked[wdgts].addWidget(self.step_text[wdgts])
self.step_stacked[wdgts].addWidget(self.step_input[wdgts])
self.top_level_layout.addWidget(self.step_stacked[wdgts])
self.top_level_layout.addWidget(self.step_label[wdgts])
self.process_button = QtGui.QPushButton("Process")
self.top_level_layout.addWidget(self.process_button, 1, 1)
self.process_button.clicked.connect(self.start_process)
self.manager = mp.Manager()
self.data = self.manager.dict(make_data())
self.Event = self.manager.Event()
self.process = mp.Process(target=some_complex_processing, args=(self.data,self.Event,))
self.process.daemon = True
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_GUI)
self.first_start = True
def update_GUI(self):
try:
for wdgt in self.data.keys():
self.step_label[wdgt].setText(str(wdgt))
self.step_input[wdgt].setText(str(self.data[wdgt]))
self.step_text[wdgt].setText(str(self.data[wdgt]))
except EOFError:
QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
self.timer.stop()
self.process.join()
return
def start_process(self):
self.Event.set()
for wdgt in self.step_stacked.keys():
self.step_stacked[wdgt].setCurrentWidget(self.step_text[wdgt])
if self.first_start==True:
print 'first start'
self.process.start()
self.first_start = False
else:
for wdgt in self.data.keys():
self.data[wdgt] = int(self.step_input[wdgt].text())
print 'start process again'
self.timer.start(100)
self.process_button.setText('Stop Processing - (manually adjust data)')
QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
return
def stop_process(self):
print 'stopping proccess'
self.Event.clear()
for wdgt in self.step_stacked.keys():
self.step_stacked[wdgt].setCurrentWidget(self.step_input[wdgt])
self.timer.stop()
self.process_button.setText('Start Processing Again Using Data')
QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
return
# GUI stuff
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self)
screen_height = app.desktop().screenGeometry().height()
screen_width = app.desktop().screenGeometry().width()
self.resize(int(screen_width*0.2), int(screen_height*0.2))
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)
self.setCentralWidget(self.centralwidget)
self.centralwidget.setLayout(self.top_level_layout)
self.process_all__button = QtGui.QPushButton("Start All Processes")
self.top_level_layout.addWidget(self.process_all__button, 0, 0)
QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
# Make Tabs in loop from button
for i in range(0,10):
super_cool_data = make_data()
name = 'tab ' + str(i)
self.tab_list.append(Tab(self.tabWidget, super_cool_data))
self.tabWidget.addTab(self.tab_list[-1], name)
def start_all_process(self):
self.process_all__button.setText('Stop All Processing')
QtCore.QObject.disconnect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.stop_all_process)
for i in self.tab_list:
i.start_process()
def stop_all_process(self):
self.process_all__button.setText('Start All Processing')
QtCore.QObject.disconnect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.stop_all_process)
QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
for i in self.tab_list:
i.stop_process()
if __name__ == "__main__":
app = QtGui.QApplication([])
win = MainWindow()
win.show()
sys.exit(app.exec_())
纵观这一大堆代码,我甚至看不到有人试图将进程或其他任何东西从子进程传递回主进程。没有队列或管道,唯一的同步对象是父级发出信号,子级等待的
事件
;进程隐式存在于i
变量中,该变量是某些复杂的处理过程的局部变量,不会传递给它之外的任何对象。multiprocessing.Manager().dict()
是否应该将字典保存在Tab对象和进程之间的共享内存中?显示使用Manager()
对象,内存在不同进程之间共享,尽管它不会递归地传播到数据类型中;我以为您正在替换数据
,而不是变异Manager.dict()
实例。不要介意。但是,有没有什么方法可以将其剥离到基本部分,只足以重现问题,而不是所有隐藏在其中的相关内容的GUI代码?同时,您只需创建一个默认间隔(0)的计时器,然后以重复模式运行它。这意味着它将在每次通过事件循环时触发。这意味着您将消耗100%的CPU来轮询dict并尽可能快地更新值。这可以解释为什么它“反应不是很快”——它花了太多时间做任何有用的事情,以至于没有时间做出平稳的反应。使用条件
或事件
向父进程发送信号,以检查值和/或在计时器上设置一个较小但非零的超时,该问题应消失。