Python3使用asyncio的run_forever时如何点击tkinter关闭按钮干净退出应用
问题根源
RequestQuit函数逻辑缺失:当前代码仅在busyProcessing为True时才会设置退出标记,若当前无运行中的异步任务,busyProcessing为默认的False,函数执行后无任何操作,点击关闭按钮不会触发任何退出流程。- 缺少
time模块导入:AfterIdle方法中调用了time.time(),但代码头部未导入time模块,运行到该逻辑时会抛出异常,终止后续退出流程。 - 第三方库兼容问题:
aiotkinter已经多年未更新,部分版本与树莓派搭载的Python 3.7环境存在适配问题,无法正确调度tkinter的窗口事件回调。 - (可选环境校验)如果窗口可以正常显示仅关闭按钮无效可忽略此条:SSH X11转发场景下需确认XQuartz开启了「允许来自网络客户端的连接」选项,且ssh连接时添加了
-X参数。
修复方案
最小改动修复(保留aiotkinter依赖)
补全代码逻辑即可,修改点如下:
- 头部添加
import time导入 - 补全
RequestQuit函数的分支逻辑,无运行中任务时直接设置退出标记
修复后完整代码:
from tkinter import * from tkinter import messagebox import aiotkinter import asyncio import time # 新增导入 afterIdleProcessingIntervalMsec = 500 # Adjust for UI responsiveness here. busyProcessing = False quitRequested = False def RequestQuit(): global quitRequested global busyProcessing if busyProcessing: answer = messagebox.askquestion('Exit application', 'Do you really want to abort the ongoing processing?', icon='warning') if answer == 'yes': quitRequested = True else: # 新增分支:无运行中任务直接标记退出 quitRequested = True def AfterIdle(): global quitRequested global loop global root if not quitRequested: root.after(afterIdleProcessingIntervalMsec, AfterIdle) else: print("Destroying GUI at: ", time.time()) try: loop.stop() root.destroy() except: exit(0) if __name__ == '__main__': global root global loop asyncio.set_event_loop_policy(aiotkinter.TkinterEventLoopPolicy()) loop = asyncio.get_event_loop() root = Tk() root.protocol("WM_DELETE_WINDOW", RequestQuit) root.after_idle(AfterIdle) # Create and pack widgets here. loop.run_forever()
无第三方依赖修复方案(推荐,兼容性更好)
放弃已经停止维护的aiotkinter,用原生逻辑整合tkinter和asyncio事件循环,适配所有Python 3.7+环境:
from tkinter import * from tkinter import messagebox import asyncio import time afterIdleProcessingIntervalMsec = 500 busyProcessing = False quitRequested = False loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) def RequestQuit(): global quitRequested global busyProcessing if busyProcessing: answer = messagebox.askquestion('Exit application', 'Do you really want to abort the ongoing processing?', icon='warning') if answer == 'yes': quitRequested = True else: quitRequested = True def AfterIdle(): global quitRequested global root if not quitRequested: root.after(afterIdleProcessingIntervalMsec, AfterIdle) else: print("Destroying GUI at: ", time.time()) try: root.destroy() loop.stop() except: exit(0) # 调度asyncio事件循环 def run_asyncio_loop(): loop.call_soon(loop.stop) loop.run_forever() if not quitRequested: root.after(10, run_asyncio_loop) if __name__ == '__main__': root = Tk() root.protocol("WM_DELETE_WINDOW", RequestQuit) root.after_idle(AfterIdle) root.after_idle(run_asyncio_loop) # Create and pack widgets here. root.mainloop()
内容的提问来源于stack exchange,提问作者zenmaster43
相关产品推荐
相关产品推荐

