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

如何实现tkinter与asyncio的协同运行?

如何让tkinter与asyncio协同工作?

这个通用问题可归结为让tkinterawait协程函数的具体问题。

背景

若tkinter的事件循环被阻塞,循环会冻结直至阻塞函数返回,对应的GUI也会冻结。传统解决方案是将阻塞代码移至线程中。

新版asyncio模块可通过协程函数asyncio.to_thread(coro)调度线程调用,避免编写正确线程代码的难点。

基准示例:blocked.py

作为起点,我编写了一个基准程序(见下方代码)。它创建一个tkinter事件循环,尝试在2000ms后销毁自身并结束程序,但被一个运行4秒的阻塞函数阻止。程序输出如下:

08:51:57: Program started.
08:51:58: blocking_func started.
08:52:02: blocking_func completed.
08:52:02: Tk event loop terminated.
08:52:02: Program ended.
Process finished with exit code 0

第一次尝试:async_blocked.py

阻塞代码被重构为协程函数,因此存在两个事件循环——tkinter和asyncio的事件循环。blocking_io_handler函数被调度到tkinter的事件循环并成功运行。协程函数blocking_func被调度到asyncio的事件循环并成功启动。

问题在于它要等到tkinter的事件循环终止后才启动。在协程函数main的整个执行过程中,asyncio的循环都是可用的,包括执行tk_root.mainloop()时。尽管如此,asyncio仍无法工作,因为tk_root.mainloop()执行期间没有通过await语句交出控制权,它必须等待后续执行的await asyncio.sleep(3)语句,而那时tkinter已停止运行。

此时,await表达式将控制权交还给异步循环3秒——足以启动运行4秒的blocking_func,但不足以让它完成。

08:38:22: Program started.
08:38:22: blocking_io_handler started.
08:38:22: blocking_io_handler completed.
08:38:24: Tk event loop terminated.
08:38:24: blocking_func started.
08:38:27: Program ended.
Process finished with exit code 0 

第二次尝试:asyncth_blocked.py

这段代码用协程函数asyncio.to_thread替换了asyncio.create_task函数,运行时出现警告:

07:26:46: Program started.
07:26:47: blocking_io_handler started.
07:26:47: blocking_io_handler completed.
RuntimeWarning: coroutine 'to_thread' was never awaited
 asyncio.to_thread(blocking_func)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
07:26:49: Tk event loop terminated.
07:26:49: Program ended.
> Process finished with exit code 0

第三次尝试:asyncth_blocked_2.py

asyncio.to_thread是协程函数而非普通函数,必须被await:await asyncio.to_thread(blocking_func)。

由于await关键字在普通函数中是语法错误,def blocking_io_handler必须改为协程函数:async def blocking_io_handler。

这些修改体现在asyncth_blocked_2.py中,程序输出如下:

07:52:29: Program started.
RuntimeWarning: 
coroutine 'blocking_io_handler' was never awaited
 func(*args)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
07:52:31: Tk event loop terminated.
07:52:31: Program ended.
Process finished with exit code 0

结论

要让tkinter与asyncio协同工作,必须将调度的函数调用tk_root.after(0, blocking_io_handler)转换为调度的协程函数调用,这是让asyncio循环有机会运行调度的异步任务的唯一方法。这是否可行?

代码

blocked.py

"""blocked.py"""
import time
import tkinter as tk


def timestamped_msg(msg: str):
    print(f"{time.strftime('%X')}: {msg}")


def blocking_func():
    timestamped_msg('blocking_func started.')
    time.sleep(4)
    timestamped_msg('blocking_func completed.')


def main():
    timestamped_msg('Program started.')
    tk_root = tk.Tk()
    tk_root.after(0, blocking_func)
    tk_root.after(2000, tk_root.destroy)
    tk_root.mainloop()
    timestamped_msg('Tk event loop terminated.')
    timestamped_msg('Program ended.')


if __name__ == '__main__':
    main()

async_blocked.py

"""async_blocked.py"""
import asyncio
import time
import tkinter as tk


def timestamped_msg(msg: str):
    print(f"{time.strftime('%X')}: {msg}")


async def blocking_func():
    timestamped_msg('blocking_func started.')
    await asyncio.sleep(4)
    timestamped_msg('blocking_func completed.')
    
    
def blocking_io_handler():
    timestamped_msg('blocking_io_handler started.')
    asyncio.create_task(blocking_func())
    timestamped_msg('blocking_io_handler completed.')


async def main():
    timestamped_msg('Program started.')
    tk_root = tk.Tk()
    tk_root.after(0, blocking_io_handler)
    tk_root.after(2000, tk_root.destroy)
    tk_root.mainloop()
    timestamped_msg('Tk event loop terminated.')
    await asyncio.sleep(3)
    timestamped_msg('Program ended.')


if __name__ == '__main__':
    asyncio.run(main())

asyncth_blocked.py

"""asyncth_blocked.py"""
import asyncio
import time
import tkinter as tk


def timestamped_msg(msg: str):
    print(f"{time.strftime('%X')}: {msg}")


async def blocking_func():
    timestamped_msg('blocking_func started.')
    await asyncio.sleep(4)
    timestamped_msg('blocking_func completed.')
    
    
def blocking_io_handler():
    timestamped_msg('blocking_io_handler started.')
    asyncio.to_thread(blocking_func)
    timestamped_msg('blocking_io_handler completed.')


async def main():
    timestamped_msg('Program started.')
    tk_root = tk.Tk()
    tk_root.after(0, blocking_io_handler)
    tk_root.after(2000, tk_root.destroy)
    tk_root.mainloop()
    timestamped_msg('Tk event loop terminated.')
    timestamped_msg('Program ended.')


if __name__ == '__main__':
    asyncio.run(main())

asyncth_blocked_2.py

"""asyncth_blocked_2.py"""
import asyncio
import time
import tkinter as tk


def timestamped_msg(msg: str):
    print(f"{time.strftime('%X')}: {msg}")


async def blocking_func():
    timestamped_msg('blocking_func started.')
    await asyncio.sleep(4)
    timestamped_msg('blocking_func completed.')
    
    
async def blocking_io_handler():
    timestamped_msg('blocking_io_handler started.')
    await asyncio.to_thread(blocking_func)
    timestamped_msg('blocking_io_handler completed.')


async def main():
    timestamped_msg('Program started.')
    tk_root = tk.Tk()
    tk_root.after(0, blocking_io_handler)
    tk_root.after(2000, tk_root.destroy)
    tk_root.mainloop()
    timestamped_msg('Tk event loop terminated.')
    timestamped_msg('Program ended.')


if __name__ == '__main__':
    asyncio.run(main())

内容的提问来源于stack exchange,提问作者lemi57ssss

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 07:09:12