编辑#3: tcaswell 已经解决了我原来的问题中的一些问题,但是现在我似乎引用了一个对象的同一个实例,而我应该有几个并行。(请参阅 tcaswell 答案的评论部分)
原始问题: 我想知道是否有人对我在将用户创建的对象传递给 GUI 时遇到的问题有任何见解,以便 GUI 将自行刷新而不是移动到“不响应”。我知道这是一个相当普遍的问题,我已经阅读了几个论坛试图了解 QThreads、信号、插槽、多处理等,但仍然遇到问题。我现在正在避免出现灰显的窗口,但是现在当我希望它在后台启动几个大型进程时,我的程序根本什么都不做。
我的项目需要在自己的进程中运行多个选项卡,但每个选项卡都有自己的数据以显示在 matplotlib 图中。我有几个按钮可以启动数据处理并显示 matplotlib 图中的变化。很多关于如何组织阈值的想法都来自这个线程。这是按下按钮后启动的功能:
# This appears to be where the problem lies because this should initialize all of the processes
def process_tabs(self):
for special_object in self.special_objects_list:
thread = QtCore.QThread(parent=self)
worker = Worker(special_object)
worker.moveToThread(thread)
worker.signal.connect(self.update_GUI)
thread.start()
return
工作人员应该在一个循环中创建一大堆信号,发送对象以更新 GUI。这是我制作的工人阶级:
# This class performs the iterative computation that needs to update the GUI
# the signals it send would *ideally* be special_obect objects so any of the parameters can be shown
class Worker(QtCore.QObject):
signal = QtCore.pyqtSignal(QtCore.QObject)
done = QtCore.pyqtSignal()
def __init__(self, special_object):
QtCore.QObject.__init__(self)
self.special_object = special_object
@QtCore.pyqtSlot()
def process_on_special_object(self):
# do a long fitting process involving the properties of the special_object
for i in range(0,99999999999999999):
self.special_object.Y += .1
self.signal.emit(self.special_object)
self.done.emit()
return
感谢您对此的任何帮助,非常感谢。
编辑: 我重新编写了代码以遵循 tcaswell 的模式,并更改了 python 插槽装饰器以将 special_objects 传递给 update_GUI 插槽。
再次编辑:我添加了 time.sleep(0.03),因此 GUI 将保持响应。这是完整形式的新代码:
import multiprocessing as mp
from PyQt4 import QtGui, QtCore
import numpy as np
import matplotlib
matplotlib.use('QtAgg')
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import figure
import sys
import lmfit
import time
# This object will just hold certain objects which will help create data objects ato be shown in matplotlib plots
# this could be a type of species with properties that could be quantized to a location on an axis (like number of teeth)
#, which special_object would hold another quantization of that property (like length of teeth)
class object_within_special_object:
def __init__(self, n, m):
self.n = n
self.m = m
def location(self, i):
location = i*self.m/self.n
return location
def NM(self):
return str(self.n) + str(self.m)
# This is what will hold a number of species and all of their properties,
# as well as some data to try and fit using the species and their properties
# I made this inherit QObject becuase I figured it *may* be more acceptable to send as a signal if the class inherited a PyQt4 class
class special_object(QtCore.QObject):
def __init__(self, name, X, Y):
QtCore.QObject.__init__(self)
self.name = name
self.X = X
self.Y = Y
self.params = lmfit.Parameters()
self.things = self.make_a_whole_bunch_of_things()
for thing in self.things:
self.params.add('something' + str(thing.NM()) + 's', value = 3)
def make_a_whole_bunch_of_things(self):
things = []
for n in range(0,20):
m=1
things.append(object_within_special_object(n,m))
return things
# a special type of tab which holds a (or a couple of) matplotlib plots and a special_object ( which holds the data to display in those plots)
class Special_Tab(QtGui.QTabWidget):
start_comp = QtCore.pyqtSignal()
def __init__(self, parent, tmp_so):
QtGui.QTabWidget.__init__(self, parent)
self.special_object = tmp_so
self.grid = QtGui.QGridLayout(self)
# matplotlib figure put into tab
self.fig = figure.Figure()
self.plot = self.fig.add_subplot(111)
self.line, = self.plot.plot(0, 0, 'r-')
self.canvas = FigureCanvas(self.fig)
self.grid.addWidget(self.canvas)
self.canvas.show()
self.canvas.draw()
self.canvas_BBox = self.plot.figure.canvas.copy_from_bbox(self.plot.bbox)
self.ax1 = self.plot.figure.axes[0]
thread = QtCore.QThread(parent=self)
self.worker = Worker(self.special_object)
self.worker.moveToThread(thread)
self.worker.update_signal.connect(self.update_GUI)
# self.worker.done_signal.connect(?)
self.start_comp.connect(self.worker.process_on_special_object)
thread.start()
@QtCore.pyqtSlot(special_object)
def update_GUI(self, tmp_so):
"""
have the tab update it's self
"""
# change the GUI to reflect changes made to special_object
self.line.set_data(tmp_so.X, tmp_so.Y)
self.ax1.set_xlim(tmp_so.X.min(), tmp_so.X.max())
self.ax1.set_ylim(0, tmp_so.Y.max() + 0.05*tmp_so.Y.max())
self.plot.draw_artist(self.line)
self.plot.figure.canvas.blit(self.plot.bbox)
def start_computation(self):
self.start_comp.emit()
# This class performs the iterative computation that needs to update the GUI
# the signals it send would *ideally* be special_obect objects so any of the parameters can be shown
class Worker(QtCore.QObject):
update_signal = QtCore.pyqtSignal(QtCore.QObject)
done_signal = QtCore.pyqtSignal()
def __init__(self, tmp_so):
QtCore.QObject.__init__(self)
self.tmp_so = tmp_so
@QtCore.pyqtSlot()
def process_on_special_object(self):
# do a long fitting process involving the properties of the special_object
for i in range(0,999):
self.tmp_so.Y += .1
time.sleep(0.03)
self.update_signal.emit(self.tmp_so)
self.done_signal.emit()
return
# This window just has a button to make all of the tabs in separate processes
class MainWindow(QtGui.QMainWindow):
process_signal = QtCore.pyqtSignal()
def __init__(self, parent = None):
# This GUI stuff shouldn't be too important
QtGui.QMainWindow.__init__(self)
self.resize(int(app.desktop().screenGeometry().width()*.6), int(app.desktop().screenGeometry().height()*.6))
self.tabs_list = []
self.special_objects_list = []
central_widget = QtGui.QWidget(self)
self.main_tab_widget = QtGui.QTabWidget()
self.layout = QtGui.QHBoxLayout(central_widget)
self.layout.addWidget(self.main_tab_widget)
button = QtGui.QPushButton('Open Tabs')
self.layout.addWidget(button)
QtCore.QObject.connect(button, QtCore.SIGNAL("clicked()"), self.open_tabs)
button2 = QtGui.QPushButton('process Tabs')
self.layout.addWidget(button2)
QtCore.QObject.connect(button2, QtCore.SIGNAL("clicked()"), self.process_tabs)
self.setCentralWidget(central_widget)
central_widget.setLayout(self.layout)
# Here we open several tabs and put them in different processes
def open_tabs(self):
for i in range(0, 10):
# this is just some random data for the objects
X = np.arange(1240.0/1350.0, 1240./200., 0.01)
Y = np.array(np.e**.2*X + np.sin(10*X)+np.cos(4*X))
# Here the special tab is created
temp_special_object = special_object(str(i), X, Y)
new_tab = Special_Tab(self.main_tab_widget, temp_special_object)
self.main_tab_widget.addTab(new_tab, str(i))
# this part works fine without the .start() function
self.tabs_list.append(new_tab)
return
# This appears to be where the problem lies because this should initialize all of the processes
def process_tabs(self):
for tab in self.tabs_list:
tab.start_computation()
return
if __name__ == "__main__":
app = QtGui.QApplication([])
win = MainWindow()
win.show()
sys.exit(app.exec_())