On Wed, 24 Feb 2021 22:35:32 +1100 John O'Hagan <resea...@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) -- https://mail.python.org/mailman/listinfo/python-list