2016-12-15 21 views
1

私は本当にPyQtでスレッドを使用する方法を理解するのに苦労しています。私は自分のUIで何をしたいのか簡単な例を作りました。下のコードでは、株価を入力する(たとえば、 "bby"、 "goog"または "v"を入力することができます)、一定期間にわたり株価をプロットします。物事はUIがより複雑になっているか、プロットが更新されている間にUIがフリーズしています。だから私はプロットを更新して、ある種の信号を受け取ったときにプロットを更新するクラスを作った(Qthread.runをオーバーライドするのは明らかに正しい方法ではないyou're doing it wrong)。私はこの "プロッタ"をメインとは別のスレッドで実行させたいと思っています。Qthreadを使ってMatplotlibの図をPyQtで更新する方法は?

スレッド行のコメントを外すと、プログラムは機能しなくなります。私は新しいスレッドの起動と "接続"を試みましたが、何も動いていません。私はdocumentationを読んでQtのWebサイトの例を見ても、Qthreadがどのように機能するのかよく分かりません。

これを行う方法がわかっている場合は、多くの手助けをします。

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

コードはスレッドセーフではありません。セカンダリスレッドからmatplotlib(または任意のQt GUI)呼び出しを行うことはできません。スレッド内のデータをフェッチすることはできますが、カスタム信号を出力してプロットするためにメインスレッドに戻す必要があります(今戻っているAxesオブジェクトではなく、プロットのデータを返してください)。 –

答えて

0

最初の問題は、それが始まっています一度あなたがthreadへの参照を失うということです(私は、Python 3.5とPyQt5で働いています)。参照を保持するには、クラス変数を使用してください。つまり、threadではなくself.threadです。

次に、何かを実行する前にスレッドを開始する必要があります。だから、シグナル放出の前にself.thread.start()を置く必要があります。

これで既に動作しますが、新しいスレッドを開始すると次の問題が発生します。だから、まず古いものを殺す必要があります。古いPlotterはホームレスになるので、プロットするたびに新しいPlotterと新しいスレッドを作成するのが解決策です。これは、以下の解決策が機能する方法です。
また、常に同じプロッタとスレッドを使用することもできます。覚えておくべき唯一のことは、常にちょうど1人の作業者(プロッタ)と1つのスレッドがあり、そのうちの1つを削除するともう1つのスレッドが悲しいということです。

これをテストするには、5の代わりにPyQt4を使用してデータ生成を置き換えるなど、いくつかの小さなものを変更する必要がありました。 ここに作業コードがあります。ここで

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_()) 

すなわち、単一のワーカースレッドを作成し、プログラムの実行時間全体でそれらを使用し、mentionned番目のオプションのためのソリューションです。

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

ありがとうございました!あなたのコードはうまくいき、私が望むのが好きですが、スレッドが決して終わらないようです。 ifステートメントにprint(True)を追加し、ティッカーを入力するたびにプログラムをループさせます(exeptは初めてです)。また、2テロップを素早く入力すると、プロットは永久に更新を停止します。 – BillyBoom

+0

また、ドキュメントではterminateの使用を推奨していません。たぶん良い解決策は、提案した第2の代替案を実行することですが、実装方法はわかりません。 – BillyBoom

+0

2番目のオプションのソリューションで更新されました。この新しいソリューションはスレッドセーフでもあります。 – ImportanceOfBeingErnest

関連する問題