Tkinter long-running window freezes

John O'Hagan research at johnohagan.com
Tue Mar 2 19:11:29 EST 2021


On Wed, 24 Feb 2021 22:35:32 +1100
John O'Hagan <research at johnohagan.com> wrote:

> Hi list
> 
> I have a 3.9 tkinter interface that displays data from an arbitrary
> number of threads, each of which runs for an arbitrary period of time.
> A frame opens in the root window when each thread starts and closes
> when it stops. Widgets in the frame and the root window control the
> thread and how the data is displayed.
> 
> This works well for several hours, but over time the root window
> becomes unresponsive and eventually freezes and goes grey. No error
> messages are produced in the terminal.
> 
> Here is some minimal, non-threaded code that reproduces the problem on
> my system (Xfce4 on Debian testing):
> 
> from tkinter import *
> from random import randint
> 
> root = Tk()
> 
> def display(label):
>     label.destroy()
>     label = Label(text=randint(0, 9))
>     label.pack()
>     root.after(100, display, label)
> 
> display(Label())
> mainloop()
>  
> This opens a tiny window that displays a random digit on a new label
> every .1 second. (Obviously I could do this by updating the text
> rather than recreating the label, but my real application has to
> destroy widgets and create new ones).
> 
> This works for 3-4 hours, but eventually the window freezes.


Thanks to those that replied to this.

To summarise my tentative and incomplete conclusions, it seems that
tkinter doesn't like a lot of widgets being created and destroyed in a
long running process and will/may eventually freeze if this is done,
for a reason I haven't been able to fathom. Tk internally keeps names
of all widgets created, and this will cause a slow memory-use increase
if not prevented by manually re-using widget names, but this is not the
cause of the freezing. 

Here is a simplified version of how I worked around the issue by
recycling widgets:

from tkinter import *

class WidgetHandler:
    "Reuse tkinter widgets"

    widget_cache = []

    def __init__(self, main_window, *args):

        self.datastream = MyDataStream(*args)

        self.main_window = main_window
        if self.widget_cache:
            self.widget = self.widget_cache.pop()
            self.clean(self.widget)
        else:
            self.widget = self.new_widget()
        #add functionality to widgets, e.g:
        self.widget.var.traceid = self.widget.var.trace('w', self.meth1)
        self.widget.button.configure(command=self.meth2)
        self.widget.button.pack()
        #etc
        self.widget.pack()

    def clean(self, widget):
        "Remove traces, clear text boxes, reset button states etc"
        #e.g:
        self.widget.var.trace_remove('write', var.traceid)
        self.widget.button['state'] = 'normal'
        #etc

    def new_widget(self):
        "Create widget and children without adding any commands, traces etc"
        widget = Frame(self.main_window)
        #e.g:
        widget.var = StringVar()
        widget.button = Checkbutton(widget, variable=widget.var)
        #etc
        return widget

    def meth1(self, *e):
        pass
        #do something with self.datastream

    def meth2(self):
        pass
        #do something else with self.datastream

    #etc

    def quit(self):
        self.datastream.quit()
        self.widget.pack_forget()
        self.widget_cache.append(self.widget)

	



More information about the Python-list mailing list