简体   繁体   中英

Itemconfigure() and memory leak with tkinter

My code (see below) is supposed to display a start/stop button, a scale button to select the speed of the visualization and a canvas with many rectangles that randomly change color with time.

When I run this piece of code, the memory usage increases dramatically with time (if you run, you need to increase the speed to about 10 to see it more easily). At work (on a Windows 7 workstation) where I tested it at first it becomes essentially unusable (it become very very slow) after few minutes while on my Mac laptop it survives a bit longer although the memory usage increases steadily.

After having looked for the culprit, I have come across multiple threads including this one from Tk Toolkit that dates back to 2010 where they mention that there is a problem with itemconfigure() when it is used to change color, which is exactly what I am doing.

Commenting the "self.update_canvas()" function in the self.run_InfiniteT_MC() function solves the problem as far as I can see and seems to agree with the diagnostic that itemconfigure() to change color may still be problematic.

Note that I have also tried to delete the changing rectangles via the command "self.canvas.delete(self.rect[i])" and then re-creating them but that does not change my memory problem at all.

I have also tried to destroy the whole canvas via "self.canvas.destroy()" and recreating everything from scratch each time in need to update the image but again that fails to solve my memory problem.

Is there anything that I can do to solve this memory problem without changing my whole code (here this just a small piece of it)?

EDIT: After indenting properly the command self.after , the problem disappeared; so the itemconfigure() command is not to blame at all, at least not for this problem.

from tkinter import *
from numpy import *
from random import randint

class Application(Frame):
    def __init__(self,master):
        Frame.__init__(self,master)
        self.columnconfigure(0, pad = 10)
        self.grid()
        self.count = 0
        self.create_widgets()


def create_widgets(self):
        "create an array of cells all initiated with the same value"
        self.nx = 70
        self.ny = self.nx
        self.ntot = self.nx*self.ny
        self.state = [0 for x in range(self.ntot)]
        for x in range(self.ntot):
            self.state[x] = 0 #0 is down, 1 is right, 2 is up, 3 is left ...modulo 4
        "create a scale button to choose speed of dynamics"
        self.ScaleSpeedVar = IntVar
        self.ScaleSpeed = Scale(self, from_=1, to =20, orient = HORIZONTAL, label = "SimuSpeed", variable = self.ScaleSpeedVar, font =('Helvetica','18'))
        self.ScaleSpeed.grid()
        self.ScaleSpeed.set(1)

"create a button that starts/stops the dynamics"
        self.do_run = False
        self.startclick = True
        self.buttonStartStop = Button(self, text = "Start/Stop", font =('Helvetica','18'))
        self.buttonStartStop["command"] = self.start_stop_simu
        self.buttonStartStop.grid()
"create a big canva to contain the simulation cells"
        self.size = 500
        self.canvas = Canvas(self, width=self.size, height=self.size, bg ="red")
        self.canvas.grid()

        self.width = 1
        self.rect = [0 for x in range(self.ntot)]
        for i in range(self.ntot):
            self.rectsize = self.size/self.nx
            self.rect[i] = self.canvas.create_rectangle((i%(self.nx))*self.rectsize, self.rectsize*(i//self.nx), (i%(self.nx))*self.rectsize+self.rectsize, self.rectsize*(i//self.nx)+self.rectsize, fill="red", tag = i, width = self.width)

def start_stop_simu(self):
    if self.startclick:
        self.start_simu()
        self.startclick = False
    else :
        self.stop_simu()
        self.startclick = True

def start_simu(self):
    self.do_run = True
    self.run_InfiniteT_MC()

def stop_simu(self):
    self.do_run = False

def run_InfiniteT_MC(self):
    if self.do_run:
        self.simuspeed = pow(2,self.ScaleSpeed.get())
        for i in range(self.simuspeed):
            self.cellID = randint(0,self.ntot-1)
            self.angle = 2*randint(0,1)-1
            self.state[self.cellID] = (self.state[self.cellID]+self.angle)%4
        self.update_canvas()
    self.after(1, self.run_InfiniteT_MC)

def update_canvas(self):
    for i in range(self.ntot):
        if self.state[i] == 0:
            self.canvas.itemconfig(self.rect[i], fill = "red")
        if self.state[i] == 2:
            self.canvas.itemconfig(self.rect[i], fill = "blue")
        if self.state[i] == 1:
            self.canvas.itemconfig(self.rect[i], fill = "green")
        if self.state[i] == 3:
            self.canvas.itemconfig(self.rect[i], fill = "yellow")
    self.canvas.update_idletasks()


root = Tk()
root.title("Problematic code")
root.geometry("800x600")
app = Application(root)

root.mainloop()

If the problem is a memory leak in the tk core when allocating colors, then the only fix is to avoid doing the itemconfigure to change the color. One solution would be to create four rectangles for each position, one of each color. You can then modify the stacking order to have the color that you want on top, which will hide the other colors.

Clearly, this solution requires four times the number of canvas objects, but unless you're drawing hundreds of thousands, it shouldn't matter. The canvas can handle tens of thousands of objects fairly well.

I believe the problem is with the call to self.after . Every time you start the simulation a new infinite loop is created (which is never stopped). Indenting the line with self.after so that it is within the if statement should resolve your issue, since then such a loop will be terminated when self.do_run becomes False .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM