EDIT: Possible solution below, can anyone confirm?
我在一个线程中运行tkinter,并尝试使用this answer中描述的 event_generate
技术来控制其他线程的行为 . 显然,一旦我启动了一个设置 tkinter.Tk
实例并启动其主循环的线程,就必须在尝试生成事件之前阻塞直到mainloop已启动 . 我试图这样做的方式如下(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 mainloop设置 threading.Event
,名为 mainloop_started
,我的事件生成代码通过阻塞开始,直到设置此事件 . 然而,这似乎导致了竞争条件 - 有时在_2529788之后快速生成的事件永远不会被tkinter处理 . 如果我在 after
调用中加时间延迟(例如 root.after(1000, mainloop_started.set)
),则不会发生这种情况,所以看起来在tkinter调用 after
回调的点和它能够接收事件的点之间有一段时间 . 有没有人知道阻塞的正确方法,直到tkinter可以接收事件?
PS:在这样的线程中运行tkinter mainloop是否安全?我知道我不能直接干扰来自其他线程的root或其他tkinter东西,但除此之外,一切似乎都可以正常工作 . 我见过一些网站说tkinter只能在主线程中运行 .
EDIT: 我想我有一个解决方案 - 使用 after_idle
而不是 after
. 这似乎确保在mainloop准备好处理事件之前不会调用回调,但任何人都可以确认这一点吗?无论如何,如果你不能保证在调用回调时完全设置tkinter(除非 after
在另一个事件处理程序中被调用,我猜), 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()