PySide2 和 Matplotlib:如何让 MatPlotLib 在单独的进程中运行? ..因为它不能在单独的线程中运行
Posted
技术标签:
【中文标题】PySide2 和 Matplotlib:如何让 MatPlotLib 在单独的进程中运行? ..因为它不能在单独的线程中运行【英文标题】:PySide2 and Matplotlib: How to make MatPlotLib run in a separate Process? ..as it cannot run in a separate Thread 【发布时间】:2019-09-24 08:10:53 【问题描述】:我不是经验丰富的程序员,我正在尝试使用 Qt for python (PySide2) 在 python 中创建一种数据记录器程序来构建 GUI。我能够使用 Designer 创建一个 gui 并将其加载到 python 中。 gui 现在只是一个空白窗口。然后我创建了一个在显示图形的窗口中启动 MatplotLib 的函数,并使用 Qt 计时器更新主程序的每个循环中的数据。
一切正常,但 MatPlotLib 的重绘时间太慢了 gui 刷新。所以我尝试将 MatPlotLib 放在一个单独的线程中,经过大量试验后我明白它不能在单独的线程中运行.. 最后我决定尝试使用多处理。现在 MatPlotLib 在单独的进程中运行良好(我使用队列将数据发送到 MatPlotLib)并在进程完成后正确退出,但是当我关闭主窗口时,程序更新完全关闭,并且还键入 Ctrl+C 提示是被屏蔽了。
这是我的代码:
#!/usr/bin/env python3
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import QFile, QTimer
import matplotlib.pyplot as plt
from multiprocessing import Process, Queue, freeze_support
import random
class DSL(QWidget):
def __init__(self):
# LOAD HMI
QWidget.__init__(self)
designer_file = QFile('userInterface.ui')
designer_file.open(QFile.ReadOnly)
loader = QUiLoader()
self.ui = loader.load(designer_file, self)
designer_file.close()
self.ui.show()
# Data to be visualized
self.data = []
def mainLoop(self):
self.data = []
for i in range(10):
self.data.append(random.randint(0, 10))
# Send data to graph process
queue.put(self.data)
# LOOP repeater
QTimer.singleShot(10, self.mainLoop)
def graphprocess(queue):
for i in range(10):
# Get data
data = queue.get()
# MatPlotLib
plt.ion()
plt.clf()
plt.plot(data)
plt.show()
plt.pause(0.1)
print('process end')
if __name__ == '__main__':
# MatPlotLib Process
queue = Queue()
freeze_support()
p = Process(target=graphProcess, args=(queue,))
p.daemon = True
p.start()
# PySide2 Process
app = QApplication(sys.argv)
dsl = DSL()
dsl.mainLoop()
sys.exit(app.exec_())
【问题讨论】:
【参考方案1】:与其在辅助进程中使用 matplotlib 不如在 QWidget 中嵌入一个画布,使其可以在相同的 PySide2 进程中运行:
#!/usr/bin/env python3
import sys
from PySide2.QtCore import QFile, QObject, Signal, Slot, QTimer
from PySide2.QtWidgets import QApplication, QVBoxLayout, QWidget
from PySide2.QtUiTools import QUiLoader
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import random
class DSL(QObject):
dataChanged = Signal(list)
def __init__(self, parent=None):
# LOAD HMI
super().__init__(parent)
designer_file = QFile("userInterface.ui")
if designer_file.open(QFile.ReadOnly):
loader = QUiLoader()
self.ui = loader.load(designer_file)
designer_file.close()
self.ui.show()
# Data to be visualized
self.data = []
def mainLoop(self):
self.data = []
for i in range(10):
self.data.append(random.randint(0, 10))
# Send data to graph
self.dataChanged.emit(self.data)
# LOOP repeater
QTimer.singleShot(10, self.mainLoop)
class MatplotlibWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
fig = Figure(figsize=(7, 5), dpi=65, facecolor=(1, 1, 1), edgecolor=(0, 0, 0))
self.canvas = FigureCanvas(fig)
self.toolbar = NavigationToolbar(self.canvas, self)
lay = QVBoxLayout(self)
lay.addWidget(self.toolbar)
lay.addWidget(self.canvas)
self.ax = fig.add_subplot(111)
self.line, *_ = self.ax.plot([])
@Slot(list)
def update_plot(self, data):
self.line.set_data(range(len(data)), data)
self.ax.set_xlim(0, len(data))
self.ax.set_ylim(min(data), max(data))
self.canvas.draw()
if __name__ == "__main__":
app = QApplication(sys.argv)
dsl = DSL()
dsl.mainLoop()
matplotlib_widget = MatplotlibWidget()
matplotlib_widget.show()
dsl.dataChanged.connect(matplotlib_widget.update_plot)
sys.exit(app.exec_())
【讨论】:
嗨 eyllanesc,非常感谢您的快速回答,这是我第一次使用 ***,我很高兴它的帮助。您编写的代码完美运行,这对我来说是向前迈出的一大步。我有一些问题想请教您的选择: - 我的选择不适合你吗? (了解我的错误..) - 是否可以同时在图中绘制更多线(我需要两条) - 如果绘图窗口关闭,我使用按钮再次显示绘图窗口,使用 matplotlib_widget.show(),是吗对 ? - 为什么如果我关闭主窗口,grap 会继续绘制? @Emilio 1) 我不会指出它是否正确,但正如您所见,还有另一个更简单的选择。 2)如果可以绘制任何图,在下面的链接中有一个示例:gist.github.com/eyllanesc/e5a2f0c86d80f4a2f241d6e3584032a9,3)是的,它是正确的,代码必须是:your_button.clicked.connect(matplotlib_widget.show)
4)尝试使用此功能的链接示例
/--------- 问题 ----------/ \n 不幸的是,更深入地测试你的代码,我在第一次实现时遇到了同样的问题创建绘图的代码只是在 DSL 类的一个函数中。如果你放更多、更多的点(例如 1000+),整个程序会变慢,包括主 GUI。这是因为正如您所说,两个窗口都在同一个进程中。这就是我试图避免的多处理。我在尝试多处理之前的代码是:
@Emilio 问题在于你的 for 循环耗时,检查链接的更新内容,你会发现它的作用相同,但效率更高。总之,分析瓶颈在哪里,尝试改进它,不要责怪代码的另一部分。以上是关于PySide2 和 Matplotlib:如何让 MatPlotLib 在单独的进程中运行? ..因为它不能在单独的线程中运行的主要内容,如果未能解决你的问题,请参考以下文章
带有 Pyside2 和 Matplotlib 的 Pyinstaller 无法正常工作
由于 PySide2 和 Matplotlib,Travis-CI 无法构建
在 Pyside2 中嵌入 Matplotlib 的释放错误
在使用 matplotlib 和 PySide2 运行的应用程序中使用 pdb 调试器时出现“事件循环已在运行”