Как блокировать, пока не будут обработаны события tkinter - PullRequest
2 голосов
/ 01 января 2012

РЕДАКТИРОВАТЬ: Возможное решение ниже, кто-нибудь может подтвердить?

Я запускаю 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...