You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter+Matplotlib动画内存泄漏及启动异常技术求助

解决回流炉控制器GUI的内存泄漏与动画启动问题

一、内存泄漏的核心原因与修复方案

你的内存持续增长问题根源很明确:每次动画循环都重复创建FigureCanvasTkAgg画布对象。在animate函数里,你每一秒都执行:

canvas = FigureCanvasTkAgg(fig,plotFrame)
canvas.draw()
canvas.get_tk_widget().grid(row=3,column=0,columnspan = 4)

这会导致每一次循环都生成一个全新的画布实例,而旧的画布因为被tkinter的布局管理器持有引用,无法被Python垃圾回收机制清理,内存自然会像你说的那样每秒飙升3.5MB,最终触发MemoryError。

修复方法非常简单:只初始化一次画布,在动画循环中仅更新绘图内容

  1. 把画布的创建代码移到全局初始化区域(和fig、ax一起),只做一次:
fig=plt.figure()
ax = fig.add_subplot(1, 1, 1)
# 初始化画布并完成布局,只执行一次
canvas = FigureCanvasTkAgg(fig, plotFrame)
canvas.get_tk_widget().grid(row=3,column=0,columnspan = 4)
  1. 修改animate函数,移除重复创建画布的代码,只保留绘图更新逻辑:
def animate(i):
    global timePoints,tempQuidance,numberOfPoints,measuredTemp, time
    time = time+1
    measuredTemp.append(readTemperature())
    numberOfPoints.append(len(measuredTemp))
    ax.clear()
    ax.plot(timePoints,tempQuidance)
    ax.plot(numberOfPoints,measuredTemp)
    updateTimeLabel(time)
    updateTargetTempLabel(calculateTarget(time))
    updateCurrentTempLabel(measuredTemp[-1])
    updateHeaterStatus(measuredTemp[-1], 100)
    # 只更新已存在的画布,不再新建
    canvas.draw()

这样修改后,整个程序生命周期内只会有一个画布实例被复用,内存就不会再持续增长了。

二、为什么必须执行animate(1)才能启动动画?

animation.FuncAnimation的默认逻辑是:启动后等待第一个interval时长(这里是1000ms),才会第一次调用animate函数。如果不提前调用animate(1),程序启动后你的图表区域会是空白的,要等整整1秒才会显示第一组数据。

提前调用animate(1)是为了让初始画面立刻渲染出来,给用户即时的视觉反馈,避免界面启动后的空白等待期。当然你也可以用FuncAnimation的init_func参数指定初始化函数,效果是完全一样的。

修改后的完整代码

import tkinter
import matplotlib.pyplot as plt
import random
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg)

def exitHandler():
    print("at exit handler")
    ani.event_source.stop()
    root.destroy()

def startButtonClick():
    print("start")

def stopButtonClick():
    print("stop")

def readTemperature():
    return random.randint(0,250)

def updateTimeLabel(time = -99):
    timeLabel.config(text= "Time: {}".format (time))

def updateTargetTempLabel(temp = -99) :
    targetTempLabel.config(text="Target temp: \n {} ".format (temp))

def updateCurrentTempLabel(temp = -99) :
    currentTempLabel.config(text="Current temp: \n {} ".format (temp))

def updateHeaterStatus(temp,target):
    if (temp < target):
        heaterStatusLabel.config(text="Heater: \n On")
    else:
        heaterStatusLabel.config(text="Heater: \n Off")

def calculateTarget(time):
    global timePoints, tempQuidance, targetTemp
    #find current slope and calculate the target temp
    for i in range (len(timePoints)):
        if (timePoints[i] < time < timePoints[i+1]):
            slope = (tempQuidance[i+1] - tempQuidance[i]) / (timePoints[i+1] -timePoints[i])
            currentTarget = (time -timePoints[i]) * slope + tempQuidance[i]
            return (currentTarget)

def animate(i):
    global timePoints,tempQuidance,numberOfPoints,measuredTemp, time
    time = time+1
    measuredTemp.append(readTemperature())
    numberOfPoints.append(len(measuredTemp))
    ax.clear()
    ax.plot(timePoints,tempQuidance)
    ax.plot(numberOfPoints,measuredTemp)
    updateTimeLabel(time)
    updateTargetTempLabel(calculateTarget(time))
    updateCurrentTempLabel(measuredTemp[-1])
    updateHeaterStatus(measuredTemp[-1], 100)
    # 仅更新已有画布
    canvas.draw()

global measuredTemp, numberOfPoints, time, targetTemp
time = 0
measuredTemp=[]
numberOfPoints=[]
targetTemp = 0
#temperature profile
timePoints = [0,300,400,460,500]
tempQuidance =[0,150,150,250,0]

root=tkinter.Tk()
root.title('Reflow oven controller')
root.geometry("1600x800")

controlFrame = tkinter.LabelFrame(root,width = 500, height = 800, borderwidth = 3, padx=5,pady=5)
plotFrame = tkinter.LabelFrame(root,padx=5,pady=5)
controlFrame.grid(row = 0, column = 0 )
controlFrame.grid_propagate(0)
plotFrame.grid(row = 0, column= 1)

timeLabel=tkinter.Label(plotFrame,text = "Time: ")
targetTempLabel = tkinter.Label(plotFrame,text = "Target temp")
currentTempLabel = tkinter.Label(plotFrame,text = "Current temp")
heaterStatusLabel = tkinter.Label(plotFrame,text = "Heater status: \n unknown")

timeLabel.grid(row= 0, column=0)
targetTempLabel.grid(row=0, column= 1)
currentTempLabel.grid(row=0, column= 2)
heaterStatusLabel.grid(row = 0, column = 3)

fig=plt.figure()
ax = fig.add_subplot(1, 1, 1)
# 初始化画布并布局
canvas = FigureCanvasTkAgg(fig, plotFrame)
canvas.get_tk_widget().grid(row=3,column=0,columnspan = 4)

startButton = tkinter.Button(controlFrame, text = "Start", command = startButtonClick)
stopButton = tkinter.Button(controlFrame, text="Stop", command = stopButtonClick)
startButton.grid(row = 0,column = 0)
stopButton.grid(row = 0 ,column= 1 )

animate(1)
ani = animation.FuncAnimation(fig, animate, interval=1000) #run animation every 1s, animate func takes care of getting new values and GUI update

root.protocol("WM_DELETE_WINDOW", exitHandler)
root.mainloop()

内容的提问来源于stack exchange,提问作者KaMa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:11:32