You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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依赖)

补全代码逻辑即可,修改点如下:

  1. 头部添加import time导入
  2. 补全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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 03:24:04