【发布时间】:2021-11-03 11:41:26
【问题描述】:
我有一个运行 CPU 密集型任务的 tkinter 应用程序。因此,为了保持 GUI 响应,我将任务放在它们自己的线程中并通过事件与 GUI 通信。这很好用,据我所知,只要我不直接从工作线程操作 tkinter 小部件和变量,它就应该是安全的。 事实上,它正在工作。 但是当我需要停止一个工作任务时,我命令工作线程停止一个名为 must_stop 的 threading.Event 并从 GUI 调用 join 。但是,当工作线程在意识到必须停止之前再生成一个事件时,连接会冻结。这很烦人。
我找到了一些方法来避免冻结使代码有些难看。我可以:
- 在 tkinter 窗口上调用 update() 时使用循环检查 thread.is_alive()。不确定这是否会破坏我的主循环
- 在每次 event_generate 调用之前检查是否已设置 must_stop(或者最好使用 threading.Lock 或 threading.Condition 在调用 join 之前由 GUI 应用)
我在下面放了一个简短的工作示例。 顺便说一句:如果我使用 event_generate 来产生事件或例如用于 GUI 的 tk.IntVar(跟踪 var 或设置标签的 textvariable - 即使它根本没有连接,也会在连接期间导致死锁)
有没有更优雅的方式让我调用 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 上测试
【问题讨论】:
标签: python multithreading tkinter events deadlock