pythonwinapitkintergreen-threads

Tkinter locks Python when an icon is loaded and tk.mainloop is in a thread


Here's the test case...

import Tkinter as tk
import thread
from time import sleep

if __name__ == '__main__':
    t = tk.Tk()
    thread.start_new_thread(t.mainloop, ())
    # t.iconbitmap('icon.ico')

    b = tk.Button(text='test', command=exit)
    b.grid(row=0)

    while 1:
        sleep(1)

This code works. Uncomment the t.iconbitmap line and it locks. Re-arrange it any way you like; it will lock.

How do I prevent tk.mainloop locking the GIL when there is an icon present?

The target is win32 and Python 2.6.2.


Solution

  • I believe you should not execute the main loop on a different thread. AFAIK, the main loop should be executed on the same thread that created the widget.

    The GUI toolkits that I am familiar with (Tkinter, .NET Windows Forms) are that way: You can manipulate the GUI from one thread only.

    On Linux, your code raises an exception:

    self.tk.mainloop(n)
    RuntimeError: Calling Tcl from different appartment
    

    One of the following will work (no extra threads):

    if __name__ == '__main__':
        t = tk.Tk()
        t.iconbitmap('icon.ico')
    
        b = tk.Button(text='test', command=exit)
        b.grid(row=0)
    
        t.mainloop()
    

    With extra thread:

    def threadmain():
        t = tk.Tk()
        t.iconbitmap('icon.ico')
        b = tk.Button(text='test', command=exit)
        b.grid(row=0)
        t.mainloop()
    
    
    if __name__ == '__main__':
        thread.start_new_thread(threadmain, ())
    
        while 1:
            sleep(1)
    

    If you need to do communicate with tkinter from outside the tkinter thread, I suggest you set up a timer that checks a queue for work.

    Here is an example:

    import Tkinter as tk
    import thread
    from time import sleep
    import Queue
    
    request_queue = Queue.Queue()
    result_queue = Queue.Queue()
    
    def submit_to_tkinter(callable, *args, **kwargs):
        request_queue.put((callable, args, kwargs))
        return result_queue.get()
    
    t = None
    def threadmain():
        global t
    
        def timertick():
            try:
                callable, args, kwargs = request_queue.get_nowait()
            except Queue.Empty:
                pass
            else:
                print "something in queue"
                retval = callable(*args, **kwargs)
                result_queue.put(retval)
    
            t.after(500, timertick)
    
        t = tk.Tk()
        t.configure(width=640, height=480)
        b = tk.Button(text='test', name='button', command=exit)
        b.place(x=0, y=0)
        timertick()
        t.mainloop()
    
    def foo():
        t.title("Hello world")
    
    def bar(button_text):
        t.children["button"].configure(text=button_text)
    
    def get_button_text():
        return t.children["button"]["text"]
    
    if __name__ == '__main__':
        thread.start_new_thread(threadmain, ())
    
        trigger = 0
        while 1:
            trigger += 1
    
            if trigger == 3:
                submit_to_tkinter(foo)
    
            if trigger == 5:
                submit_to_tkinter(bar, "changed")
    
            if trigger == 7:
                print submit_to_tkinter(get_button_text)
    
            sleep(1)