2016-12-15 4 views
1

Ich habe wirklich eine harte Zeit zu verstehen, wie Sie Threads in PyQt verwenden. Ich habe ein einfaches Beispiel dafür erstellt, was ich in meiner Benutzeroberfläche tun möchte. In dem Code, den Sie unten sehen können, möchte ich, dass der Benutzer einen Börsenticker eingibt (Sie können z. B. "bby", "goog" oder "v" eingeben) und den Wert des Wertpapiers über einen bestimmten Zeitraum grafisch darstellen. Die Sache ist in komplexeren Ui oder für eine lange Zeit die Benutzeroberfläche eingefroren, während der Plot aktualisiert wird. Also habe ich eine "Plotter" -Klasse erstellt, die das Diagramm aktualisiert, wenn es ein bestimmtes Signal empfängt (das Überschreiben von Qthread.run war anscheinend nicht der richtige Weg you're doing it wrong). Ich möchte diesen "Plotter" in einem anderen Thread als dem Hauptprogramm laufen lassen.Wie verwende ich Qthread, um eine Matplotlib-Figur mit PyQt zu aktualisieren?

Sobald ich die Fadenlinien auskommentiere, hört das Programm auf zu arbeiten. Ich habe versucht, den Start des neuen Threads und auch das "Verbinden" zu verschieben, aber nichts funktioniert. Ich denke, ich verstehe nicht gut, wie Qthread funktioniert, nachdem ich die documentation gelesen und die Beispiele auf der Qt-Website angeschaut habe.

Wenn Sie wissen, wie dies zu tun ist, würde es sehr helfen! (Ich arbeite mit Python 3.5 und PyQt5)

from PyQt5.QtCore import * 
from PyQt5.QtWidgets import * 
from matplotlib.axes._subplots import Axes 
from matplotlib.figure import Figure 
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 
import sys 
from datetime import datetime, timedelta 
import time 
import quandl 


class MyMplCanvas(FigureCanvas): 
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.).""" 
    send_fig = pyqtSignal(Axes, str, name="send_fig") 

    def __init__(self, parent=None): 
     self.fig = Figure() 
     self.axes = self.fig.add_subplot(111) 

     # We want the axes cleared every time plot() is called 
     self.axes.hold(False) 

     FigureCanvas.__init__(self, self.fig) 
     self.setParent(parent) 

     FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) 
     FigureCanvas.updateGeometry(self) 

    def update_plot(self, axes): 
     self.axes = axes 
     self.draw() 

class MainWindow(QMainWindow): 
    send_fig = pyqtSignal(Axes, str, name="send_fig") 

    def __init__(self): 
     super().__init__() 

     self.main_widget = QWidget(self) 
     self.myplot = MyMplCanvas(self.main_widget) 
     self.editor = QLineEdit() 
     self.display = QLabel("Vide") 

     self.layout = QGridLayout(self.main_widget) 
     self.layout.addWidget(self.editor) 
     self.layout.addWidget(self.display) 
     self.layout.addWidget(self.myplot) 

     self.main_widget.setFocus() 
     self.setCentralWidget(self.main_widget) 

     self.move(500, 500) 
     self.show() 

     self.editor.returnPressed.connect(self.updatePlot) 

     self.plotter = Plotter() 
     self.send_fig.connect(self.plotter.replot) 

     self.plotter.return_fig.connect(self.myplot.update_plot) 


    def updatePlot(self): 
     ticker = self.editor.text() 
     self.editor.clear() 
     self.display.setText(ticker) 

     # thread = QThread() 
     # self.plotter.moveToThread(thread) 

     self.send_fig.emit(self.myplot.axes, ticker) 

     # thread.start() 


class Plotter(QObject): 
    return_fig = pyqtSignal(Axes) 

    @pyqtSlot(Axes, str) 
    def replot(self, axes, ticker): # A slot takes no params 
     print(ticker) 
     d = datetime.today() - timedelta(weeks=52) # data from 1week ago 
     data = quandl.get("YAHOO/"+ticker+".6", start_date=d.strftime("%d-%m-%Y"), end_date=time.strftime("%d-%m-%Y")) 
     axes.plot(data) 
     self.return_fig.emit(axes) 


if __name__ == '__main__': 
    app = QApplication(sys.argv) 
    win = MainWindow() 
    sys.exit(app.exec_()) 
+0

Ihr Code ist nicht threadsicher. Sie können keine Matplotlib-Aufrufe (oder Qt-GUI-Aufrufe) von einem sekundären Thread ausführen. Sie können die Daten in einem Thread abrufen, aber Sie müssen sie zum Zeichnen an den Hauptthread zurücksenden, indem Sie ein benutzerdefiniertes Signal ausgeben (geben Sie also die Daten zum Plotten und nicht das Achsenobjekt zurück, das Sie gerade zurückgeben). –

Antwort

0

Das erste Problem ist, dass Sie den Verweis auf thread verlieren, sobald es gestartet wird. Um eine Referenz zu behalten, verwenden Sie eine Klassenvariable, d. H. self.thread anstelle von thread.

Als nächstes muss der Thread gestartet werden, bevor etwas unternommen wird. Also müssen Sie self.thread.start() vor der Signalemission setzen.

Jetzt würde es bereits funktionieren, aber ein nächstes Problem tritt auf, wenn Sie einen neuen Thread starten möchten. Also musst du zuerst den alten töten. Da die alte dann obdachlos wäre, ist eine Lösung, einen neuen Plotter sowie einen neuen Thread jedes Mal zu erstellen, wenn Sie plotten möchten. So funktioniert die folgende Lösung.
Alternativ können Sie auch immer den gleichen Plotter und Thread verwenden. Die einzige Sache, an die man sich erinnert, ist, dass es immer genau einen Arbeiter (Plotter) und einen Faden gibt, wenn man einen von ihnen löscht, ist der andere traurig.

Um es zu testen, musste ich einige kleine Dinge ändern, wie PyQt4 anstelle von 5 verwenden und die Datengenerierung ersetzen. Hier ist der Arbeitscode.

from PyQt4.QtCore import * 
from PyQt4.QtGui import * 
from matplotlib.axes._subplots import Axes 
from matplotlib.figure import Figure 
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas 
import sys 
from datetime import datetime, timedelta 
import numpy as np 



class MyMplCanvas(FigureCanvas): 
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.).""" 
    send_fig = pyqtSignal(Axes, str, name="send_fig") 

    def __init__(self, parent=None): 
     self.fig = Figure() 
     self.axes = self.fig.add_subplot(111) 

     # We want the axes cleared every time plot() is called 
     self.axes.hold(False) 

     FigureCanvas.__init__(self, self.fig) 
     self.setParent(parent) 

     FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) 
     FigureCanvas.updateGeometry(self) 

    def update_plot(self, axes): 
     self.axes = axes 
     self.draw() 

class MainWindow(QMainWindow): 
    send_fig = pyqtSignal(Axes, str, name="send_fig") 

    def __init__(self): 
     super(MainWindow, self).__init__() 

     self.main_widget = QWidget(self) 
     self.myplot = MyMplCanvas(self.main_widget) 
     self.editor = QLineEdit() 
     self.display = QLabel("Vide") 

     self.layout = QGridLayout(self.main_widget) 
     self.layout.addWidget(self.editor) 
     self.layout.addWidget(self.display) 
     self.layout.addWidget(self.myplot) 

     self.main_widget.setFocus() 
     self.setCentralWidget(self.main_widget) 

     self.move(500, 500) 
     self.show() 

     self.editor.returnPressed.connect(self.updatePlot) 

     # plotter and thread are none at the beginning 
     self.plotter = None 
     self.thread = None 



    def updatePlot(self): 
     ticker = self.editor.text() 
     self.editor.clear() 
     self.display.setText(ticker) 

     # if there is already a thread running, kill it first 
     if self.thread != None and self.thread.isRunning(): 
      self.thread.terminate() 

     # initialize plotter and thread 
     # since each plotter needs its own thread 
     self.plotter = Plotter() 
     self.thread = QThread() 
     # connect signals 
     self.send_fig.connect(self.plotter.replot) 
     self.plotter.return_fig.connect(self.myplot.update_plot) 
     #move to thread and start 
     self.plotter.moveToThread(self.thread) 
     self.thread.start() 
     # start the plotting 
     self.send_fig.emit(self.myplot.axes, ticker) 



class Plotter(QObject): 
    return_fig = pyqtSignal(Axes) 

    @pyqtSlot(Axes, str) 
    def replot(self, axes, ticker): # A slot takes no params 
     print(ticker) 
     d = datetime.today() - timedelta(weeks=52) # data from 1week ago 
     # do some random task 
     data = np.random.rand(10000,10000) 
     axes.plot(data.mean(axis=1)) 
     self.return_fig.emit(axes) 


if __name__ == '__main__': 
    app = QApplication(sys.argv) 
    win = MainWindow() 
    sys.exit(app.exec_()) 

Hier ist eine Lösung für die zweite Option erwähnt, das heißt einen einzelnen Arbeiter und einen Thread erstellen und diese während des gesamten Programms Laufzeit verwenden.

from PyQt4.QtCore import * 
from PyQt4.QtGui import * 
from matplotlib.figure import Figure 
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas 
import sys 
import numpy as np 



class MyMplCanvas(FigureCanvas): 

    def __init__(self, parent=None): 
     self.fig = Figure() 
     self.axes = self.fig.add_subplot(111) 
     # plot empty line 
     self.line, = self.axes.plot([],[], color="orange") 

     FigureCanvas.__init__(self, self.fig) 
     self.setParent(parent) 

     FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) 
     FigureCanvas.updateGeometry(self) 


class MainWindow(QMainWindow): 
    send_fig = pyqtSignal(str) 

    def __init__(self): 
     super(MainWindow, self).__init__() 

     self.main_widget = QWidget(self) 
     self.myplot = MyMplCanvas(self.main_widget) 
     self.editor = QLineEdit() 
     self.display = QLabel("Vide") 

     self.layout = QGridLayout(self.main_widget) 
     self.layout.addWidget(self.editor) 
     self.layout.addWidget(self.display) 
     self.layout.addWidget(self.myplot) 

     self.main_widget.setFocus() 
     self.setCentralWidget(self.main_widget) 
     self.show() 

     # plotter and thread are none at the beginning 
     self.plotter = Plotter() 
     self.thread = QThread() 

     # connect signals 
     self.editor.returnPressed.connect(self.start_update) 
     self.send_fig.connect(self.plotter.replot) 
     self.plotter.return_fig.connect(self.plot) 
     #move to thread and start 
     self.plotter.moveToThread(self.thread) 
     self.thread.start() 

    def start_update(self): 
     ticker = self.editor.text() 
     self.editor.clear() 
     self.display.setText(ticker) 
     # start the plotting 
     self.send_fig.emit(ticker) 


    # Slot receives data and plots it 
    def plot(self, data): 
     # plot data 
     self.myplot.line.set_data([np.arange(len(data)), data]) 
     # adjust axes 
     self.myplot.axes.set_xlim([0,len(data) ]) 
     self.myplot.axes.set_ylim([ data.min(),data.max() ]) 
     self.myplot.draw() 


class Plotter(QObject): 
    return_fig = pyqtSignal(object) 

    @pyqtSlot(str) 
    def replot(self, ticker): 
     print(ticker) 
     # do some random task 
     data = np.random.rand(10000,10000) 
     data = data.mean(axis=1) 
     self.return_fig.emit(data) 


if __name__ == '__main__': 
    app = QApplication(sys.argv) 
    win = MainWindow() 
    sys.exit(app.exec_()) 
+0

Vielen Dank! Dein Code funktioniert und möchte, dass ich möchte, aber es scheint, als würden die Threads nie enden. Ich habe einen Ausdruck (True) in der if-Anweisung hinzugefügt und das Programm geht in diese Schleife jedes Mal, wenn Sie einen Ticker eingeben (außer beim ersten Mal). Wenn Sie schnell 2 Ticker eingeben, wird der Plot nicht mehr aktualisiert. – BillyBoom

+0

Auch die Verwendung von Beenden wird in der Dokumentation nicht empfohlen. Vielleicht ist die gute Lösung, die zweite Alternative zu machen, die Sie vorgeschlagen haben, aber ich bin mir nicht sicher, wie ich sie umsetzen soll. – BillyBoom

+0

Aktualisiert mit einer Lösung für die zweite Option. Diese neue Lösung ist auch threadsicher. – ImportanceOfBeingErnest

Verwandte Themen