Python Tkinter热键启动连点器后点击停止按钮程序冻结问题
异常触发原因
- 核心问题为线程安全冲突:Pynput的热键监听器运行在独立子线程中,你直接将App类的
start_click方法作为回调传给热键,导致该方法实际在Pynput的监听器子线程中执行,不符合Tkinter所有组件相关操作必须运行在主线程的要求。 Clicker类中的running、prog_running状态变量没有加线程锁,多线程(Clicker连点线程、Pynput监听器线程、Tkinter主线程)同时读写该变量时会出现状态竞争,极端情况下会触发死锁,表现为整个程序冻结。- 点击界面Start按钮时,
start_click是在Tkinter主线程中执行的,仅涉及主线程修改状态、Clicker线程读状态,没有多线程写冲突,所以运行正常;而热键触发时多了一个子线程写状态的操作,点击Stop按钮时主线程也要修改状态,两个线程同时竞争变量资源就会触发死锁,导致程序卡住。
修复方案
- 热键回调不要直接执行
start_click,而是通过Tkinter主线程的after方法将调用投递到主线程执行,保证所有状态修改操作都在Tkinter主线程完成:
在App类中新增热键触发的中转方法:
# App类新增方法 def hotkey_trigger(self): # after第一个参数为延迟毫秒,0表示立即投递到主线程执行 self.parent.after(0, self.start_click)
初始化Keyboard实例时传入新的中转方法:
self.kb = Keyboard("<shift>+s", self.hotkey_trigger)
- 给Clicker类的状态变量加线程锁,避免多线程读写竞争:
import threading class Clicker(threading.Thread): def __init__(self, button, delay): super().__init__() self.button = button self.delay = delay # 新增线程锁 self.lock = threading.Lock() self._running = False self._prog_running = True self.mouse = Controller() @property def running(self): with self.lock: return self._running @running.setter def running(self, value): with self.lock: self._running = value @property def prog_running(self): with self.lock: return self._prog_running @prog_running.setter def prog_running(self, value): with self.lock: self._prog_running = value def start_click(self): print("start") self.running = True def stop_click(self): print("stop") self.running = False def exit(self): self.running = False self.prog_running = False def run(self): while self.prog_running: while self.running: self.mouse.click(self.button) time.sleep(self.delay) time.sleep(0.1)
内容的提问来源于stack exchange,提问作者Huy Truong Le
相关产品推荐
相关产品推荐

