在PyQt GUI中嵌入和更新matplotlib图时发生内存泄漏

问题描述:

我正在尝试将每秒更新一次的 matplotlib 图嵌入到 PyQt GUI 主窗口中.

I am trying to embed a matplotlib graph that updates every second into a PyQt GUI main window.

在我的程序中,我通过 timer 函数使用 threading.Timer 每秒调用一次更新函数.我有一个问题:我的程序每秒都在变大 - 以每 4 秒大约 1k 的速度增长.我最初的想法是append函数(在 update_figure 中返回新数组)不会删除旧数组吗?这可能是我遇到问题的原因吗?

In my program I call an update function every second using threading.Timer via the timer function shown below. I have a problem: my program grows bigger every second - at a rate of about 1k every 4 seconds. My initial thoughts are that the append function (that returns a new array in update_figure) does not delete the old array? Is it possible this is the cause of my problem?

def update_figure(self):
    self.yAxis = np.append(self.yAxis, (getCO22()))
    self.xAxis = np.append(self.xAxis, self.i)
    # print(self.xAxis)
    if len(self.yAxis) > 10:
        self.yAxis = np.delete(self.yAxis, 0)

    if len(self.xAxis) > 10:
        self.xAxis = np.delete(self.xAxis, 0)

    self.axes.plot(self.xAxis, self.yAxis, scaley=False)
    self.axes.grid(True)

    self.i = self.i + 1

    self.draw()

这是我的计时器功能 - 这是通过单击我的 PyQt GUI 中的按钮触发的,然后如您所见地调用自身:

This is my timer function - this is triggered by the click of a button in my PyQt GUI and then calls itself as you can see:

def timer(self):
    getCH4()
    getCO2()
    getConnectedDevices()
    self.dc.update_figure()
    t = threading.Timer(1.0, self.timer)
    t.start()

我不能发布我的整个代码,因为它需要包含很多 .dll.所以我会试着解释这个程序的作用.

I cant post my entire code because it requires a lot of .dll includes. So i'll try to explain what this program does.

在我的GUI中,我想显示一段时间内的CO 2 值.我的 get_co22 函数只返回一个浮点值,我 100% 确定这可以正常工作.使用我的计时器,如上所示,我想继续将一个值附加到 matplotlib 图 - Axes 对象对我来说是 self.axes.我尝试绘制数据的最后10个值.

In my GUI I want to show the my CO2 value over time. My get_co22 function just returns a float value and I'm 100% sure this works fine. With my timer, shown above, I want to keep append a value to a matplotlib graph - the Axes object is available to me as self.axes. I try to plot the last 10 values of the data.

经过 http://pastebin.com/RXya6Zah .这将代码的结构更改为将 update_figure()调用为以下代码:

EDIT 2: After some discussion in chat, I tried putting the call to update_figure() in a while loop and using just one thread to call it and was able to make this minimal example http://pastebin.com/RXya6Zah. This changed the structure of the code to call update_figure() to the following:

def task(self):
    while True:
        ui.dc.update_figure()
        time.sleep(1.0)

def timer(self):
    t = Timer(1.0, self.task())
    t.start()

但是现在程序在大约5次迭代后崩溃.

but now the program crashes after 5 iterations or so.

问题绝对不在于您如何附加到numpy数组或将其截断.

The problem is definitely not with how you are appending to your numpy array, or truncating it.

这里的问题在于您的线程模型.很难将计算循环与GUI控制循环集成在一起.

The problem here is with your threading model. Integrating calculation loops with a GUI control loop is difficult.

从根本上说,您需要 GUI 线程来控制何时调用更新代码(如有必要,生成一个新线程来处理它)- 这样

Fundamentally, you need your GUI threading to have control of when your update code is called (spawning a new thread to handle it if necessary) - so that

  1. 您的代码不会阻止GUI更新,
  2. GUI 更新不会阻止您的代码执行并且
  3. 您不会产生包含多个对象副本的线程负载(这可能是内存泄漏的来源).

在这种情况下,由于您的主窗口由 PyQt4 控制,您需要使用 QTimer(请参阅 此处的简单示例)

In this case, as your main window is controlled by PyQt4, you want to use a QTimer (see a simple example here)

所以 - 将您的 timer 代码更改为

So - alter your timer code to

def task(self):
    getCH4()
    getCO2()
    getConnectedDevices()
    self.dc.update_figure()

def timer(self):
    self.t = QtCore.QTimer()
    self.t.timeout.connect(self.task)
    self.t.start(1000)

这应该有效.保持对 QTimer 的引用是必不可少的 - 因此 self.t = QtCore.QTimer() 而不是 t = QtCore.QTimer(),否则 QTimer 对象将被垃圾回收.

and this should work. Keeping the reference to the QTimer is essential - hence self.t = QtCore.QTimer() rather than t = QtCore.QTimer(), otherwise the QTimer object will be garbage collected.

这是的长线程的摘要聊天,以澄清问题并尝试几种可能的解决方案.特别是 - OP 设法在这里模拟了一个更简单的可运行示例:http://pastebin.com/RXya6Zah

This is a summary of a long thread in chat clarifying the issue and working through several possible solutions. In particular - the OP managed to mock up a simpler runnable example here: http://pastebin.com/RXya6Zah

完整可运行示例的固定版本在这里:http://pastebin.com/gv7Cmapr

and the fixed version of the full runnable example is here: http://pastebin.com/gv7Cmapr

上面有相关的代码和解释,但是这些链接可能会对想要复制/解决问题的人有所帮助.请注意,它们需要安装PyQt4

The relevant code and explanation is above, but the links might help anyone who wants to replicate / solve the issue. Note that they require PyQt4 to be installed