Ткинтер - утечка памяти с помощью canvas - PullRequest
1 голос
/ 08 марта 2019

У меня есть скрипт Python, который обрабатывает связь Modbus. Одной из функций, которую я добавил, был «график», который показывает время отклика вместе с цветной линией, которая указывает, был ли ответ успешным, имел ли исключение или ошибку. График - это просто прокручиваемый виджет холста от Tkinter.

После построения графика определенного количества строк старые строки будут удалены, а затем добавлена ​​новая в конец. Для этого примера у меня установлено значение 10, что означает, что на холсте никогда не будет более 10 строк одновременно.

Код работает правильно, но где-то в этой функции есть утечка памяти. Я позволил ему проработать около 24 часов, а через 24 часа потребовалось примерно в 6 раз больше памяти. Функция является частью большего класса.

Мое текущее предположение состоит в том, что мой код заставляет размер холста постоянно «расширяться», что медленно пожирает память.

self.lineList = []
self.xPos = 0

def UpdateResponseTimeGraph(self):
    if not self.graphQueue.empty():
        temp = self.graphQueue.get() #pull from queue. A separate thread handles calculating the length and color of the line. 
        self.graphQueue.task_done()

        lineName     = temp[0] #assign queue values to variables
        lineLength   = temp[1]
        lineColor    = temp[2]

        if len(self.lineList) >= 10: #if more than 10 lines are on the graph, delete the first one.
            self.responseTimeCanvas.delete(self.lineList[0])
            del self.lineList[0]

        #Add line to canvas and a list so it can be referenced.
        self.lineList.append(self.responseTimeCanvas.create_rectangle(self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-lineLength, 
                                                fill=lineColor, outline=''))

        self.xPos += 5 #will cause the next line to start 5 pixels later. MEMORY LEAK HERE?

        self.responseTimeCanvas.config(scrollregion=self.responseTimeCanvas.bbox(ALL))

        self.responseTimeCanvas.xview_moveto(1.0) #move to the end of the canvas which is scrollable.



    self.graphFrame.after(10, self.UpdateResponseTimeGraph)

Одним решением может быть возврат к началу графа, как только будет достигнут предел, но я бы предпочел этого не делать, так как это может привести к путанице в начале графа. Обычно у меня гораздо больше ответов, чем 10.

EDIT:

Я все еще занимаюсь отслеживанием и ошибками, но похоже, что утечка памяти может быть устранена предложением Брайана, если атрибуты строки не изменяются с помощью itemconfig. Код ниже должен быть в состоянии работать как есть, если вы используете Python 2.7, измените оператор импорта с tkinter на Tkinter (строчные и прописные t). В этом коде будет утечка памяти. Закомментируйте строку itemconfig, и она будет удалена.

import tkinter
from tkinter import Tk, Frame, Canvas, ALL
import random

def RGB(r, g, b):
    return '#{:02x}{:02x}{:02x}'.format(r, g, b)

class MainUI:
    def __init__(self, master):
        self.master = master
        self.lineList = []
        self.xPos = 0

        self.maxLine = 122

        self.responseIndex = 0 


        self.responseWidth = 100
        self.responseTimeCanvas = Canvas(self.master, height=self.responseWidth)
        self.responseTimeCanvas.pack()

        self.UpdateResponseTimeGraph()

    def UpdateResponseTimeGraph(self):
        self.lineLength   = random.randint(10,99)

        if len(self.lineList) >= self.maxLine:
            self.lineLength = random.randint(5,95)
            self.responseTimeCanvas.coords(self.lineList[self.responseIndex % self.maxLine], self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-self.lineLength)

            #if i comment out the line below the memory leak goes away.
            self.responseTimeCanvas.itemconfig(self.lineList[self.responseIndex % self.maxLine], fill=RGB(random.randint(0,255), random.randint(0,255), random.randint(0,255)))
        else:
            self.lineList.append(self.responseTimeCanvas.create_rectangle(self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-self.lineLength, 
                                                fill=RGB(random.randint(0,255), random.randint(0,255), random.randint(0,255)), outline=''))


        self.xPos += 5 #will cause the next line to start 5 pixels later. MEMORY LEAK HERE?
        self.responseIndex += 1

        self.responseTimeCanvas.config(scrollregion=self.responseTimeCanvas.bbox(ALL))

        self.responseTimeCanvas.xview_moveto(1.0) #move to the end of the canvas which is scrollable.



        self.responseTimeCanvas.after(10, self.UpdateResponseTimeGraph)


mw = Tk()
mainUI = MainUI(mw)
mw.mainloop()

Ответы [ 2 ]

1 голос
/ 08 марта 2019

Базовый tk canvas не использует и не перерабатывает идентификаторы объектов.Всякий раз, когда вы создаете новый объект, генерируется новый идентификатор.Память об этих объектах никогда не восстанавливается.

Примечание: это память во встроенном интерпретаторе tcl, а не память, управляемая python.

Решение состоит в том, чтобы переконфигурировать старые, больше не используемые элементы, а не удалять их и создавать новые.

0 голосов
/ 09 марта 2019

Вот код без утечки памяти. Первоначальным источником утечки было то, что я удалил старую строку, а затем создал новую. Это решение перемещает сначала строку в конец, а затем изменяет ее атрибуты по мере необходимости. У меня была вторая «утечка» в моем примере кода, когда я каждый раз выбирал случайный цвет, что приводило к тому, что количество используемых цветов поглощало много памяти. Этот код просто печатает зеленые линии, но длина будет случайной.

import tkinter
from tkinter import Tk, Frame, Canvas, ALL
import random

def RGB(r, g, b):
    return '#{:02x}{:02x}{:02x}'.format(r, g, b)

class MainUI:
    def __init__(self, master):
        self.master = master
        self.lineList = []
        self.xPos = 0

        self.maxLine = 122

        self.responseIndex = 0 


        self.responseWidth = 100
        self.responseTimeCanvas = Canvas(self.master, height=self.responseWidth)
        self.responseTimeCanvas.pack()

        self.UpdateResponseTimeGraph()

    def UpdateResponseTimeGraph(self):
        self.lineLength   = random.randint(10,99)

        if len(self.lineList) >= self.maxLine:
            self.lineLength = random.randint(5,95)
            self.responseTimeCanvas.coords(self.lineList[self.responseIndex % self.maxLine], self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-self.lineLength)

            self.responseTimeCanvas.itemconfig(self.lineList[self.responseIndex % self.maxLine], fill=RGB(100, 255, 100))
        else:
            self.lineList.append(self.responseTimeCanvas.create_rectangle(self.xPos, self.responseWidth, self.xPos + 4, self.responseWidth-self.lineLength, 
                                                fill=RGB(100, 255, 100), outline=''))


        self.xPos += 5 #will cause the next line to start 5 pixels later. 
        self.responseIndex += 1

        self.responseTimeCanvas.config(scrollregion=self.responseTimeCanvas.bbox(ALL))

        self.responseTimeCanvas.xview_moveto(1.0) #move to the end of the canvas which is scrollable.



        self.responseTimeCanvas.after(10, self.UpdateResponseTimeGraph)


mw = Tk()
mainUI = MainUI(mw)
mw.mainloop()
...