РЕДАКТИРОВАТЬ: Возможное решение ниже, кто-нибудь может подтвердить?
Я запускаю tkinter в потоке, и пытаюсь использовать метод event_generate
, описанный в этот ответ чтобы контролировать его поведение из других потоков.Ясно, что после того, как я запустил поток, который настраивает экземпляр tkinter.Tk
и запускает его основной цикл, необходимо блокировать его до тех пор, пока основной цикл не запустится, прежде чем пытаться генерировать события.Я пытался сделать это следующим образом (код Python 3.2):
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]
Другими словами, я использую after
, чтобы заставить основной поток tkinter установить threading.Event
с именем mainloop_started
, и мой код, генерирующий события, начинается с блокировки, пока не будет установлено это событие.Однако это, похоже, приводит к состоянию гонки - иногда события, генерируемые быстро после mainloop_started.wait()
, никогда не обрабатываются tkinter.Если я добавлю задержку в вызове after
(например, root.after(1000, mainloop_started.set)
), этого не произойдет, поэтому похоже, что между точкой, в которой tkinter вызывает обратный вызов after
, и точкой, в которой есть времяон может получать события.Кто-нибудь знает правильный способ блокировки, пока tkinter не сможет получать события?
PS: безопасно ли запускать основной поток tkinter внутри потока, подобного этому?Я знаю, что не могу напрямую вмешиваться в root или другие вещи из tkinter из других потоков, но кроме этого все, кажется, работает нормально.Я видел некоторые сайты, которые говорят, что tkinter можно запустить только в главном потоке.
EDIT: Я думаю, что у меня есть решение - используйте after_idle
вместо after
.Кажется, это гарантирует, что обратный вызов не будет вызван, пока основной цикл не будет готов обрабатывать события, но может ли кто-нибудь это подтвердить?В любом случае, какой смысл after
, если вы не можете гарантировать, что tkinter будет полностью настроен при вызове обратного вызова (если, я полагаю, after
не вызывается в другом обработчике событий)?
Конкретный пример эффектов использования after
и after_idle
в этом случае, если кто-то хочет поиграть с ним:
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()