Python asyncio:to_thread阻塞函数后置await无法执行的问题与解决
问题1:为何先await计数器任务、再await to_thread协程时控制台输入无法执行?
asyncio.to_thread(get_console_input)返回的是协程对象,而非已启动运行的任务。直接await协程对象时,只有当前协程(这里是main)执行到该行,才会开始运行该协程的逻辑。
如果先执行await t_counter(t_counter是用create_task创建的任务),await任务会让main协程挂起,直到该任务完成。但你的counter()是无限循环的协程,永远不会结束,所以main会一直卡在await t_counter这一行,根本没机会走到await t_console的步骤——控制台输入的逻辑自然永远不会被执行。
反过来,若先await t_console(to_thread返回的协程),to_thread会把阻塞的input()放到单独线程执行,这个协程会快速挂起并让出事件循环,此时main协程会继续创建并启动计数器任务,事件循环会同时处理线程里的输入等待和计数器的定时打印,因此两者能同时工作。
问题2:为何用create_task包装to_thread协程后,两种await顺序都正常?
asyncio.create_task()的核心作用是把协程对象立即调度到事件循环中启动运行,和后续await的顺序无关。当你把to_thread返回的协程用create_task包装成t_console任务后,只要调用了create_task,这个任务就已经在事件循环里启动(线程里的input()开始等待输入),不管你后续先await哪个任务。
比如:
- 先
await t_console:t_counter已被create_task启动,事件循环会同时运行两个任务,直到输入x结束t_console,再继续等待t_counter。 - 先
await t_counter:t_console已被create_task启动,线程里的input()已在等待,计数器也在定时打印,两者同时工作,直到输入x让t_console完成,main才会继续等待t_counter。
简单说,create_task是让任务“提前进入事件循环排队运行”,而直接await协程是“走到这一步才让协程开始运行”,这就是两者的核心区别。
修正后的完整代码
""" Goal: simultaneously print a counter and accept keyboard input, using async concepts """ import asyncio def get_console_input(): """ Get console input forever This gets invoked by asyncio.to_thread since input() is blocking And therefore this does not get the async keyword """ while True: s = input('> ') print(s) if s == 'x': return async def counter(): """ Print ascending counter every 1sec """ i = 0 while True: await asyncio.sleep(1) print(i) i += 1 async def main(): # 将to_thread返回的协程包装为任务,确保提前调度运行 coro_console = asyncio.to_thread(get_console_input) t_console = asyncio.create_task(coro_console) t_counter = asyncio.create_task(counter()) # 两种await顺序均可正常运行 await t_counter await t_console if __name__ == '__main__': asyncio.run(main())
内容的提问来源于stack exchange,提问作者rob_7cc

