編集 #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_())