EDIT: Possible solution below, can anyone confirm?
I'm running tkinter in a thread, and attempting to use the event_generate
technique described in this answer to control its behaviour from other threads. Clearly, once I have started a thread which sets up a tkinter.Tk
instance and starts its mainloop, it is necessary to block until the mainloop has started before attempting to generate events. The way I was trying to do this is as follows (python 3.2 code):
import tkinter
import threading
mainloop_started = threading.Event()
def tkinter_thread_stuff():
global root
root = tkinter.Tk()
#[code that binds events with root.bind]
root.after(0, mainloop_started.set)
root.mainloop()
th = threading.Thread(target=tkinter_thread_stuff)
th.start()
mainloop_started.wait()
#[code that sends events to root with event_generate]
In other words, I am using after
to cause the tkinter mainloop to set the threading.Event
called mainloop_started
, and my event-generating code starts by blocking until this event is set. However, this seems to lead to a race condition - sometimes events generated quickly after mainloop_started.wait()
are never processed by tkinter. If I put a time delay in the after
call (e.g. root.after(1000, mainloop_started.set)
), this does not happen, so it looks like there is some time in between the point where tkinter calls the after
callback, and the point at which it is able to receive events. Does anyone know the correct way of blocking until tkinter can receive events?
PS: is it safe to run the tkinter mainloop inside a thread like this? I know I can't interfere directly with root or other tkinter stuff from other threads, but apart from that, everything seems to work OK. I have seen some sites that say tkinter can only be run in the main thread.
EDIT: I think I have a solution - use after_idle
instead of after
. This seems to make sure that the callback isn't called until the mainloop is ready to process events, but can anyone confirm this? Anyway, what is the point of after
if you can't guarantee that tkinter will be fully set up when the callback is called (unless after
is called in another event handler, I suppose)?
A concrete example of the effects of using after
and after_idle
in this case if anyone wants to play around with it:
import tkinter
import threading
#create a threading.Event for blocking until the mainloop is ready
mainloop_started = threading.Event()
#counter stores the number of times that tkinter receives the event
#<<increment>>, which is generated 1000 times
counter = 0
#delay in milliseconds before mainloop sets mainloop_started:
#if I set this to 0, the final value of counter varies between 0 and 1000, i.e.
#some of the <<increment>> events may not be processed as expected
#if I set it to 1 or a larger number, the final value of counter is always 1000,
#so all events are processed correctly, and I can get the
#same behaviour by replacing after with after_idle below
delay_ms = 0
def send_events():
global root
#wait until mainloop_started has been set
mainloop_started.wait()
#send 1000 <<increment>> events
for i in range(1000):
root.event_generate('<<increment>>', when='tail')
#send a <<show>> event
root.event_generate('<<show>>', when='tail')
#run send_events in a thread
th = threading.Thread(target=send_events)
th.start()
#start tkinter
root = tkinter.Tk()
#when root receives the event <<increment>>, increment counter
def increment(event):
global counter
counter += 1
root.bind('<<increment>>', increment)
#when root receives the event <<show>>, print the value of the counter
def show(event):
print(counter)
root.bind('<<show>>', show)
#instruct mainloop to set mainloop_started
root.after(delay_ms, mainloop_started.set)
#using after_idle instead causes all events to be processed correctly
#root.after_idle(mainloop_started.set)
#finally, start the mainloop
root.mainloop()