Python tkinter定时器首次结束后程序崩溃且提示音循环播放问题求助
问题原因分析
- 根本问题出在
setTimer()函数对window.after()的使用逻辑错误:window.after()是异步注册接口,调用后会立刻返回不会阻塞等待定时结束,所以你在for循环里批量调用after()的效果是一次性注册了9个定时器:4次循环共8个1分钟触发的定时器、1个30分钟触发的定时器,所有定时器的计时起点都是setTimer()被调用的瞬间。 - 当1分钟时间到后,8个
timerDone()回调会几乎同时触发,自然会出现提示音连续循环播放的情况,而playsound默认是同步阻塞执行,大量回调同时调用会直接卡死tkinter主事件循环,最终导致程序崩溃,后续逻辑自然无法执行。 - 额外影响:
playsound默认同步播放的逻辑会阻塞tkinter的界面刷新,哪怕只有单个定时器触发,也会出现界面假死的问题。
修复方案
- 放弃批量注册定时器的逻辑,改为链式触发:每次
timerDone()执行完成后,再注册下一次需要的定时器,保证任务按顺序执行 - 调用
playsound时添加block=False参数(需要playsound 1.3.0及以上版本),让音频异步播放,不阻塞主事件循环;如果版本不支持该参数,可以用子线程播放音频 - 新增定时器ID存储变量,重置/切换定时器时可以取消未触发的任务,避免重复注册
修正后的核心代码示例
import threading # 低版本playsound需要用线程播放时导入 # 新增变量存储定时器ID,方便取消 current_timer_id = None def timerDone(): global onBreak, isTimerDone, pomodoros, current_timer_id isTimerDone = True onBreak = not onBreak if onBreak: reminderTitle.config(text="Go on break!") # 休息时间到了之后触发下一轮工作计时,5分钟短休息可按需调整 current_timer_id = window.after(5 * 60 * 1000, timerDone) else: pomodoros += 1 reminderTitle.config(text="Get to work!") if pomodoros < 4: # 没到4轮番茄钟就继续25分钟工作计时 current_timer_id = window.after(25 * 60 * 1000, timerDone) else: # 到4轮就触发30分钟长休息 pomodoros = 0 reminderTitle.config(text="Take a long break!") current_timer_id = window.after(30 * 60 * 1000, timerDone) timer.config(text="Completed") # 异步播放音频,二选一即可 # 高版本playsound用这个: playsound(f'{os.getcwd()}/Audio/notification.mp3', block=False) # 低版本playsound用线程方案: # threading.Thread(target=playsound, args=(f'{os.getcwd()}/Audio/notification.mp3',), daemon=True).start() def setTimer(mins=25): global pomodoros, onBreak, current_timer_id # 先取消之前的定时器,避免重复触发 if current_timer_id is not None: window.after_cancel(current_timer_id) pomodoros = 0 onBreak = False timer.config(text="In Progress") reminderTitle.config(text="Get to work!") # 只注册第一个定时器,后续链式触发 current_timer_id = window.after(mins * 60 * 1000, timerDone) def resetTimer(): global current_timer_id, pomodoros, onBreak if current_timer_id is not None: window.after_cancel(current_timer_id) current_timer_id = None pomodoros = 0 onBreak = False timer.config(text="Not Started") reminderTitle.config(text="There are no current reminders.")
内容的提问来源于stack exchange,提问作者Daniel Portas-Levy
相关产品推荐
相关产品推荐

