在 Tkinter 回调中启动的线程会阻塞整个界面
Thread started in a Tkinter Callback block the entire interface
(发现新信息后删除了旧的描述,我认为不再需要它了)。
所以,我有一个 Tkinter 界面,实际上有三个按钮:运行、停止、步骤。
运行 按钮启动一个运行各种功能的线程,但需要在步骤按钮上输入以继续执行。这是通过等待 Python 事件来完成的,该事件在您按下步骤按钮时设置。
然而,尽管在等待我的事件时有 time.sleep
,我的代码没有 return 到 Tkinter 界面,整个程序被阻止。如果我删除等待条件,线程会一直运行直到它停止,然后我就可以访问我的界面。
我相信回调函数 RunButton()
不会 return 直到线程结束,这就是我的界面被锁定的原因。有什么办法可以解决这个问题吗?
这是我当前的代码:
import sys
import signal
import inspect
import threading
import multiprocessing
import Queue
import time
import Tkinter
class tk_interface(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid()
self.button1 = Tkinter.Button(self,text=u"Run",command=self.RunButton)
self.button1.grid(column=0,row=0)
self.button2 = Tkinter.Button(self,text=u"Stop",command=self.StopButton)
self.button2.grid(column=1,row=0)
self.button3 = Tkinter.Button(self,text=u"Step",command=self.StepButton)
self.button3.grid(column=2,row=0)
self.cmd_box = Tkinter.Text(self, wrap='word', height=20)
self.cmd_box.grid(column=0,row=1,columnspan=3)
self.resizable(False,False)
self.update()
self.ReadReportQueue()
def RunButton(self):
thr1.run()
def StopButton(self):
self.destroy()
def StepButton(self):
cmd_step.set()
def ReadReportQueue(self):
sys.stdout=sys.__stdout__
if not reportQueue.empty():
catch_message(self.cmd_box,reportQueue)
self.after(200, self.ReadReportQueue)
class debug_thread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def trace_calls(self, frame, event, arg):
if event != 'call':
return
co = frame.f_code
func_name = co.co_name
file_name = co.co_filename
if func_name in ['loop_func']:
return self.trace_lines
def trace_lines(self, frame, event, arg):
if event != 'line' and event != 'return':
return
co = frame.f_code
func_name = co.co_name
file_name = co.co_filename
source = inspect.getsourcelines(co)[0]
#cmd_step.wait()
while not cmd_step.is_set():
time.sleep(0.2)
cmd_step.clear()
print('Call to %s on line %s of %s' % (func_name, frame.f_lineno, co.co_filename))
def loop_func(self):
for i in range(0,3):
print("Loop number %d\n" %i)
def run(self):
print "Started thread"
sys.stdout = reportQueue
sys.settrace(self.trace_calls)
self.loop_func()
print "Exiting " + self.name
class StdoutQueue(Queue.Queue):
def __init__(self, *args, **kwargs):
Queue.Queue.__init__(self, *args, **kwargs)
def write(self, msg):
self.put(msg)
def flush(self):
sys.__stdout__.flush()
def catch_message(text_widget, queue):
text_widget.insert(Tkinter.INSERT,queue.get())
if __name__ == "__main__":
cmd_step = threading.Event()
cmd_step.clear()
reportQueue = StdoutQueue()
thr1 = debug_thread('Thread 1')
app = simpleapp_tk(None)
app.title('Debug App')
while True:
app.update_idletasks()
app.update()
print "updating..."
当您通过从 threading.Thread
继承来创建线程时,您通过覆盖 Thread.run
提供线程中应该 运行 的代码。但是,debug_thread.run
仍然是一个正常的方法。当您调用 thr1.run()
时,您只是在执行 debug_thread.run
中声明的代码,它不会启动单独的线程。要启动一个单独的线程,您需要使用 thr1.start()
,这将创建一个新线程并在新线程中调用您的 run
方法。
从 threading.Thread
继承的另一种方法是将要在新线程中调用的函数作为参数传递给 threading.Thread
构造函数。
def run_in_thread(step_event, stop_event):
local_data = 1
while not stop_event.is_set():
local_data = do_step(local_data)
step_event.wait()
step_event.clear()
step_event = threading.Event()
stop_event = threading.Event()
thread = threading.Thread(target=run_in_thread, args=(step_event, stop_event))
thread.start()
要单步运行线程函数,只需设置 step_event
,要停止它并让线程终止,设置 stop_event
这样循环就会中断 then设置 step_event
这样线程将再次检查循环条件。
两个 Event
作为线程参数传递,因此它们不需要是全局变量。
(发现新信息后删除了旧的描述,我认为不再需要它了)。
所以,我有一个 Tkinter 界面,实际上有三个按钮:运行、停止、步骤。
运行 按钮启动一个运行各种功能的线程,但需要在步骤按钮上输入以继续执行。这是通过等待 Python 事件来完成的,该事件在您按下步骤按钮时设置。
然而,尽管在等待我的事件时有 time.sleep
,我的代码没有 return 到 Tkinter 界面,整个程序被阻止。如果我删除等待条件,线程会一直运行直到它停止,然后我就可以访问我的界面。
我相信回调函数 RunButton()
不会 return 直到线程结束,这就是我的界面被锁定的原因。有什么办法可以解决这个问题吗?
这是我当前的代码:
import sys
import signal
import inspect
import threading
import multiprocessing
import Queue
import time
import Tkinter
class tk_interface(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid()
self.button1 = Tkinter.Button(self,text=u"Run",command=self.RunButton)
self.button1.grid(column=0,row=0)
self.button2 = Tkinter.Button(self,text=u"Stop",command=self.StopButton)
self.button2.grid(column=1,row=0)
self.button3 = Tkinter.Button(self,text=u"Step",command=self.StepButton)
self.button3.grid(column=2,row=0)
self.cmd_box = Tkinter.Text(self, wrap='word', height=20)
self.cmd_box.grid(column=0,row=1,columnspan=3)
self.resizable(False,False)
self.update()
self.ReadReportQueue()
def RunButton(self):
thr1.run()
def StopButton(self):
self.destroy()
def StepButton(self):
cmd_step.set()
def ReadReportQueue(self):
sys.stdout=sys.__stdout__
if not reportQueue.empty():
catch_message(self.cmd_box,reportQueue)
self.after(200, self.ReadReportQueue)
class debug_thread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def trace_calls(self, frame, event, arg):
if event != 'call':
return
co = frame.f_code
func_name = co.co_name
file_name = co.co_filename
if func_name in ['loop_func']:
return self.trace_lines
def trace_lines(self, frame, event, arg):
if event != 'line' and event != 'return':
return
co = frame.f_code
func_name = co.co_name
file_name = co.co_filename
source = inspect.getsourcelines(co)[0]
#cmd_step.wait()
while not cmd_step.is_set():
time.sleep(0.2)
cmd_step.clear()
print('Call to %s on line %s of %s' % (func_name, frame.f_lineno, co.co_filename))
def loop_func(self):
for i in range(0,3):
print("Loop number %d\n" %i)
def run(self):
print "Started thread"
sys.stdout = reportQueue
sys.settrace(self.trace_calls)
self.loop_func()
print "Exiting " + self.name
class StdoutQueue(Queue.Queue):
def __init__(self, *args, **kwargs):
Queue.Queue.__init__(self, *args, **kwargs)
def write(self, msg):
self.put(msg)
def flush(self):
sys.__stdout__.flush()
def catch_message(text_widget, queue):
text_widget.insert(Tkinter.INSERT,queue.get())
if __name__ == "__main__":
cmd_step = threading.Event()
cmd_step.clear()
reportQueue = StdoutQueue()
thr1 = debug_thread('Thread 1')
app = simpleapp_tk(None)
app.title('Debug App')
while True:
app.update_idletasks()
app.update()
print "updating..."
当您通过从 threading.Thread
继承来创建线程时,您通过覆盖 Thread.run
提供线程中应该 运行 的代码。但是,debug_thread.run
仍然是一个正常的方法。当您调用 thr1.run()
时,您只是在执行 debug_thread.run
中声明的代码,它不会启动单独的线程。要启动一个单独的线程,您需要使用 thr1.start()
,这将创建一个新线程并在新线程中调用您的 run
方法。
从 threading.Thread
继承的另一种方法是将要在新线程中调用的函数作为参数传递给 threading.Thread
构造函数。
def run_in_thread(step_event, stop_event):
local_data = 1
while not stop_event.is_set():
local_data = do_step(local_data)
step_event.wait()
step_event.clear()
step_event = threading.Event()
stop_event = threading.Event()
thread = threading.Thread(target=run_in_thread, args=(step_event, stop_event))
thread.start()
要单步运行线程函数,只需设置 step_event
,要停止它并让线程终止,设置 stop_event
这样循环就会中断 then设置 step_event
这样线程将再次检查循环条件。
两个 Event
作为线程参数传递,因此它们不需要是全局变量。