I am designing an application using PyQt5. There are a lot of plots and I use Matplotlib to manage them.
Currently, I'm trying to use Qthread
so that the Main Loop
does not freeze for too long.
I used This exemple: How to use a Qthread to update a Matplotlib figure with PyQt?.
But, as the autor mentioned it, in the first solution, the threads I create never stop.
My question is: How can I stop it when the replot
function is done in the Plotter()
object ?
I do not like the use of Terminate()
in this code.
Also the fact that the QThread
is finished will allow me to set a PushButton
able again using a connection like self.thread.finished.connect(my_function)
.
Here are some simplified portions of my code:
Class figure to creates the figures
# Figures
class My_figure(FigureCanvas):
send_fig = QtCore.pyqtSignal(Axes, name="send_fig")
def __init__(self, fig_size, fig_move, parent=None):
self.fig = Figure(figsize=fig_size)
self.axes = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
self.move(fig_move[0], fig_move[1])
def update_plot(self, axes):
self.axes = axes
self.draw()
Functions in my main dialog
# graphs creation
def new_graph(self, name):
setattr(self, name + '_my_fig', [])
# créer les graphs, axes, canvas, etc.
graph_cat = ['bar_', 'pie_','line_']
graph_element = ['_figure', '_canvas', '_axes', '_plotter', '_thread']
fig_dim = [[0, (6.8, 4.3), (6, 3.5)],[0, 0, 30]]
compt = 0
for g_c in graph_cat:
getattr(self, name + '_my_fig').append([])
setattr(self, g_c + name + '_my_fig', My_figure((3,3),[0,0], parent = getattr(self, g_c + name)))
getattr(self, name + '_my_fig')[-1].append(getattr(self, g_c + name + '_my_fig'))
# Couple thread / worker
setattr(self, g_c + name + graph_element[3], None)
setattr(self, g_c + name + graph_element[4], None)
getattr(self, name + '_my_fig')[-1].append(getattr(self, g_c + name + graph_element[3]))
getattr(self, name + '_my_fig')[-1].append(getattr(self, g_c + name + graph_element[4]))
compt += 1
# thread and worker creation + start
for my_fig in self.tot_my_fig:
print(my_fig)
# if there is already a thread running, kill it first
if my_fig[1] != None and my_fig[1].isRunning():
print('termiante')
my_fig [1].terminate()
my_fig[1] = QtCore.QThread()
my_fig[2] = Plotter()
self.send_fig.connect(my_fig[2].replot)
my_fig[2].return_fig.connect(my_fig[0].update_plot)
my_fig[1].finished.connect(self.ppp)
#move to thread and start
my_fig[2].moveToThread(my_fig[1])
my_fig[1].start()
# start the plotting
self.send_fig.emit(my_fig[0].axes)
Workers's class
# Worker
class Plotter(QtCore.QObject):
return_fig = QtCore.pyqtSignal(Axes)
@QtCore.pyqtSlot(Axes)
def replot(self, axes): # A slot takes no params
axes.clear()
# do some random task
data = np.random.rand(1000,1000)
axes.plot(data.mean(axis=1))
self.return_fig.emit(axes)
Edit:
I found the answer, thank to https://stackoverflow.com/a/6789205/12016306
I just needed to send a signal when the worker is done and connect this signal to the quit()
method of the thread. Like this:
class Worker(QObject):
finished = pyqtSignal()
def do_the_work(self):
# do the long task and even the plotting
self.finished.emit()
class MainWindow(QMainWindow):
def __init__(self):
#Normal init goes here
#construction of the thread and worker
self.worker = Worker()
self.thread = Qthread()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.do_the_work)
self.worker.finished.connect(self.thread.quit)
self.thread.finished.connect(self.print_when_finished)
self.thread.start() # You can start it whenever you want. Put it in a function to start the worker only after a certain action.