python - Tkinter event_generate 冻结线程连接
问题描述
我有一个运行 CPU 密集型任务的 tkinter 应用程序。因此,为了保持 GUI 响应,我将任务放在它们自己的线程中并通过事件与 GUI 通信。这很好用,据我所知,只要我不直接从工作线程操作 tkinter 小部件和变量,它就应该是安全的。事实上,它正在工作。但是当我需要停止一个工作任务时,我命令工作线程停止一个名为must_stop的threading.Event并从 GUI 调用 join 。但是,当工作线程在意识到必须停止之前再生成一个事件时,连接会冻结。这很烦人。
我找到了一些避免冻结的方法,使代码有些难看。我可以:
- 在 tkinter 窗口上调用update()时使用循环检查thread.is_alive()。不确定这是否会破坏我的主循环
- 在每个event_generate调用之前检查must_stop是否已设置(或者在调用 join 之前使用 GUI 应用的threading.Lock或threading.Condition可能更好)
我在下面放了一个简短的工作示例。顺便说一句:如果我使用event_generate来产生一个事件或例如用于 GUI 的tk.IntVar(跟踪 var 或设置标签的文本变量- 即使它根本没有连接,它也会导致加入期间死锁)
有没有更优雅的方式让我在没有死锁的情况下调用thread.join() ?还是与 tkinter GUI 进行通信的更好概念?据我所知,tkinter 事件被称为“线程安全”。
import threading
import tkinter as tk
import time
must_stop = threading.Event()
counter_lock = threading.Lock()
counter = 0
def run_thread(root):
#Threaded procedure counting seconds and generating events for the root window whenever
#the variable changes
global counter
while not must_stop.is_set():
time.sleep(1)
with counter_lock:
counter += 1
root.event_generate('<<Counter>>', when = 'tail')
class CounterWindow(tk.Tk):
#Window class for the counter
def __init__(self):
super().__init__()
self.label = tk.Label(self, text = 'Hello!')
self.label.pack()
self.button = tk.Button(text = 'Start counter', command = self.start_thread)
self.button.pack()
self.bind('<<Counter>>', self.update_counter)
def update_counter(self, event):
#Writes counter to label, triggered by <<Counter>> event
with counter_lock:
self.label.configure(text = counter) # replacing this line
#print(counter) # with a tk-free routine does not prevent deadlock
def start_thread(self):
#Button command to start the thread
self.thread = threading.Thread(target = run_thread, args = (self, ))
self.thread.start()
self.button.configure(text = 'Stop counter', command = self.stop_thread)
def stop_thread(self):
#Button command to stop the thread. Attention: Causing deadlock !!!
#self.unbind('<<Counter>>') # does not prevent deadlock either
must_stop.set()
self.thread.join() # replacing this line
#while self.thread.is_alive(): # with this loop prevents deadlock
# self.update()
self.button.configure(text = 'Exit counter', command = self.destroy)
#Start the app
window = CounterWindow()
window.mainloop()
使用 python 版本 3.9.5。在 Windows 和 linux 上测试
解决方案
这不会给我任何错误并且运行良好:
import threading
import tkinter as tk
import time
must_stop = threading.Event()
counter_lock = threading.Lock()
counter = 0
def run_thread(root):
global counter
while not must_stop.is_set():
time.sleep(1)
with counter_lock:
counter += 1
if must_stop.is_set():
return None
root.event_generate('<<Counter>>', when="tail")
class CounterWindow(tk.Tk):
# Window class for the counter
def __init__(self):
super().__init__()
self.label = tk.Label(self, text="Hello!")
self.label.pack()
self.button = tk.Button(text="Start counter", command=self.start_thread)
self.button.pack()
self.bind("<<Counter>>", self.update_counter)
def update_counter(self, event):
#Writes counter to label, triggered by <<Counter>> event
with counter_lock:
self.label.configure(text=counter)
def start_thread(self):
#Button command to start the thread
self.thread = threading.Thread(target=run_thread, args=(self, ))
self.thread.start()
self.button.configure(text="Stop counter", command=self.stop_thread)
def stop_thread(self):
must_stop.set()
# self.thread.join() # Don't think it's nessasary
self.button.configure(text="Exit counter", command=self.destroy)
# Start the app
window = CounterWindow()
window.mainloop()
我在调用之前添加了另一个检查,.event_generate()
以便在must_stop
设置时不会生成事件。我也删除了.join()
,我不是threading
专家,但据我所知,这并不是真正必要的。
您的代码更大的问题是tkinter
它本身不是线程安全的。试试这个例子。即使没有tkinter
从另一个线程调用任何方法,它也会使 python 解释器崩溃。tkinter
在某些情况下,仅将小部件/变量传递给另一个线程可能会导致麻烦。
推荐阅读
- pentaho - Row Flatener 未正确处理 NULL 值
- git - 如何恢复 git add?
- string - 如何解决flutter错误String不是int类型的子类型
- python - Python。用于在地图上存储对象坐标的数据结构
- json - 从云形成器创建的 cft 云形成错误
- javascript - 在Raphael中垂直对齐两个元素
- python - 在熊猫中,如何将自 1970 年 1 月 1 日以来的毫秒转换为数据类型时间戳?
- c# - 为异步调用保存的通用参数在哪里?在哪里可以找到它的名称或其他信息?
- cucumber - 浏览器退出和线程的黄瓜并行执行问题
- aws-lambda - 创建 CloudWatch 警报,通过 SNS/Lambda 将实例设置为备用