0

I recently started testing out Tkinter to make simple apps that can hook into an API and give a user a sampling of data from that API. One thing I wanted was for this window to refresh occasionally, updating the information so that it's never too far out of date.

However, it seems that the tkinter window is crashing regularly. No errors or messages are returned, it just becomes unresponsive at erratic times. Adding large numbers of widgets makes the crash happen sooner, but with different numbers of widgets it seems to happen eventually regardless.

To make it autorefresh at intervals, I set up a thread that would call the tkinter App's redraw method. From my research I know that tkinter isn't thread safe, but I thought that wouldn't be an issue when I have just the one thread every 30 seconds calling a redraw method that takes a moment to run.

Here's a minimal version of my app that will crash after a few attempted redraws. My actual data has only about 15 widgets and crashes eventually, but raising the number to very high values makes the crashes happen earlier and makes it easier to test with.

from Tkinter import *

from threading import Thread, Event

REFRESH_INTERVAL = 30

class App(Frame):

    names = ["test"] * 50

    def __init__(self, master):
        Frame.__init__(self, master)

        self.frame = master
        self.table = None
        self.redraw()


    def redraw(self):
        if self.table:
            self.table.destroy()

        new_frame = Frame(self.frame)
        new_frame.pack()
        self.table = new_frame

        for code in self.names:
            label = Label(new_frame, text=code)
            label.pack()


class Refresher(Thread):

    def __init__(self, event, app):
        Thread.__init__(self)
        self.stopped = event
        self.app = app

    def run(self):
        while not self.stopped.wait(REFRESH_INTERVAL):
            print("Refreshing...")
            self.app.redraw()


def main():
    root = Tk()
    app = App(root)
    app.pack(side=TOP, fill="both", expand=True)

    stop_flag = Event()
    refresher = Refresher(stop_flag, app)
    refresher.start()
    root.mainloop()
    stop_flag.set()


if __name__ == "__main__":
    main()
SuperBiasedMan
  • 9,814
  • 10
  • 45
  • 73
  • 2
    tkinter doesn't work well with threads. You shouldn't be calling any tkinter functions or widget methods from any threads but the thread that created the root window. You don't need threads just for a simple timer. Read up on the `after` method. – Bryan Oakley Oct 10 '16 at 17:20
  • @BryanOakley Ah, the `after` method does seem to be working for me, testing it now. Do you want to post that as an answer? – SuperBiasedMan Oct 11 '16 at 09:45

1 Answers1

0

It turns out that Tkinter doesn't like separate threads making calls like this. Instead, it is recommended to use the after method instead.

This answer goes into detail on it, but it's effectively a way of calling a function after a delay. It can be added into my redraw function at the send, to constantly add a trigger to rerun that function after a delay.

def redraw(self):
    if self.table:
        self.table.destroy()

    new_frame = Frame(self.frame)
    new_frame.pack()
    self.table = new_frame

    for code in self.names:
        label = Label(new_frame, text=code)
        label.pack()
    self.master.after(REFRESH_INTERVAL, self.redraw)
Community
  • 1
  • 1
SuperBiasedMan
  • 9,814
  • 10
  • 45
  • 73