我真的很难理解如何在PyQt中使用线程。我做了一个简单的示例,说明我想在UI中执行的操作。在下面的代码中,我希望用户输入一个股票行情自动收录器(例如,您可以输入“ bby”,“ goog”或“ v”)并绘制特定时期内的股票价值。问题是在更复杂的Ui中,或者很长一段时间UI冻结,而绘图正在更新。于是我做了一个“绘图仪”类更新时收到一定的信号(覆盖Qthread.run显然是不正确的做法情节你这样做是错误的)。我想让这个“绘图仪”在另一个线程之外运行。
一旦取消注释线程程序,程序就会停止工作。我试图移动新线程的启动以及“连接”,但没有任何反应。我认为即使阅读文档并查看Qt网站上的示例,我也不太了解Qthread的工作原理。
如果您知道如何执行此操作,将会很有帮助!(我正在使用Python 3.5和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_())
第一个问题是,thread一旦启动,您将丢失对它的引用。要保留引用,请使用类变量,即self.thread代替thread。
thread
self.thread
接下来,必须在执行任何操作之前启动线程。因此,您需要将self.thread.start()信号发射放在前面。
self.thread.start()
现在,它已经可以工作了,但是一旦您要启动新线程,就会出现下一个问题。因此,您需要先杀死旧的。由于旧的Plotter将无家可归,因此一个解决方案是每次要绘制时都创建一个新的Plotter以及一个新线程。这是下面的解决方案的工作方式。 另外,您也可以始终使用相同的绘图仪和线程。唯一要记住的是,始终只有一个工作程序(绘图仪)和一个线程,如果删除其中一个,则另一个很可悲。
Plotter
为了测试它,我需要更改一些小东西,例如使用PyQt4而不是5并替换数据生成。这是工作代码。
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_())
这是提到的第二个选项的解决方案,即创建单个工作程序和线程,并在程序的整个运行时使用它们。
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_())