the_eraser
the_eraser

Reputation: 425

Tkinter and thread. out of stack space (infinite loop?)

I'm experimenting with Tkinter and the threads mechanism. Can anyone explain why this raises the exception:

<class '_tkinter.TclError'> out of stack space (infinite loop?)

and how can I solve this? Below is the code. BTW, I know some people suggest to use the threading module instead of thread, but for now I'd like to use the thread module which is simpler just to introduce myself to the mechanism.

from Tkinter import *
import thread
import time

def main_thread(master):

    try:
        frame = Frame(master)
        frame.pack(side='bottom')
        scrollbar = Scrollbar(master)
        scrollbar.pack(side='right',fill='y')

        t = "Title"
        title = StringVar()
        title.set(t)
        ttl = Label(master, textvariable=title, font=("Helvetica", 18))
        ttl.pack(side='top')
        cont = Text(master, font=("Helvetica",14), yscrollcommand=scrollbar.set)
        cont.pack(side='bottom')
        button = Button(frame,text="Exit", command=root.destroy)
        button.pack(side='bottom')

        n = 0
        while 1:
            n += 1
            cont.insert('end', str(n)+"\n")
            time.sleep(1)

    except Exception as e:
        print type(e), e

if __name__ == '__main__':

    root = Tk()
    root.title("My counting application")
    thread.start_new_thread(main_thread, (root,)) # FIXME: out of stack space (infinite loop?)
    root.mainloop()

Thank you, Luca


EDIT

I solved substituting

    n = 0
    while 1:
        n += 1
        cont.insert('end', str(n)+"\n")
        time.sleep(1)

with

    n = 0
    def do_every_second(n):
        cont.insert("end", str(n) + "\n")
        n += 1
        master.after(1000, do_every_second, n)
    do_every_second(n)

and calling

main_thread(root)

instead of

thread.start_new_thread(main_thread, (root,))

Upvotes: 6

Views: 6293

Answers (2)

ubomb
ubomb

Reputation: 11581

The error is correct - there's an infinite loop on one of the tkinter elements. The thread module has nothing to do with it. The specific line causing the error:

cont.insert('end', str(n)+"\n")

Since cont is a tkinter element, you can not run it in your infinite while loop. To do what you want, you would need to print to the console instead. This can be demonstrated if you replace the offending line with a simple:

print(str(n)+"\n")

Edit: If you truly want to achieve the same effect you originally intended, this post deals with a potential alternative.

Edit2: I would assume the exception is a design choice by the tkinter library (although I'm no expert). This would make sense since tkinter already uses an infinite loop for its event loop. I would imagine an infinite loop would prevent tkinter from ever drawing the changes to the screen and instead the libraries authors chose to throw an exception instead. A print should work since there's nothing new to draw, plus it's in it's own thread allowing tkinter's event loop to continue.

Upvotes: -1

Bryan Oakley
Bryan Oakley

Reputation: 385970

You have a couple of fatal flaws in your code. For one, you simply can't write code that touches tkinter widgets from more than one thread. You are creating the root window in the main thread, so you can only ever directly access widgets from the main thread. Tkinter is not thread safe.

The second problem is that you have an infinite loop that is constantly appending to the text widget. It has no choice but to eventually run out of memory.

To solve your problem you should:

  1. not have an infinite loop that forever appends to the text widget
  2. not access any widgets from more than a single thread

If you want to run a function once a second, there are better ways to do that than with threads. In short:

def do_every_second():
    cont.insert("end", str(n) + "\n")
    root.after(1000, do_every_second)

This will cause do_every_second to do whatever it does, then arranges for itself to be called again one second in the future.

Upvotes: 4

Related Questions