使用`run_in_executor`创建任务未显式等待,为何Python进程仍会等待任务完成?
run_in_executor背后的线程池逻辑 首先直接给结论:你的预期(进程在打印main finished后立即退出)是合理的,但默认情况下asyncio的行为会阻止这种情况发生,而你提到的loop.run_until_complete(loop.shutdown_default_executor())确实是解决这个问题的正确方式。
为什么进程没有立即退出?
当你调用loop.run_in_executor(None, sync_sleep, 4)时,asyncio会使用默认的ThreadPoolExecutor来执行同步函数。这个线程池里的线程默认是非守护线程——在Python中,主线程结束时,非守护线程会继续运行直到任务完成,才会让整个进程退出。
所以即使你的事件循环已经通过run_until_complete(main())执行完毕(打印了main finished),executor里的线程还在后台跑sync_sleep(4),进程必须等这个线程任务结束才会终止,这就是为什么你会看到sync_sleep awake在main finished之后打印。
你的解决方案是对的!
添加loop.run_until_complete(loop.shutdown_default_executor())确实能让事件循环等待默认executor中所有已提交的任务完成。修改后的代码示例如下:
import asyncio import time def sync_sleep(x): print("sync_sleep going to sleep") time.sleep(x) print("sync_sleep awake") async def main(): loop = asyncio.get_running_loop() loop.run_in_executor(None, sync_sleep, 4) print("main going to sleep") await asyncio.sleep(1) print("main awake ") if __name__ == '__main__': loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) # 等待默认executor的所有任务执行完成 loop.run_until_complete(loop.shutdown_default_executor()) finally: loop.close() print("main finished")
执行这段代码的输出会变成:
sync_sleep going to sleep main going to sleep main awake sync_sleep awake main finished
此时进程会在打印main finished后立即退出,因为executor已经关闭,所有后台任务都已完成。
额外选项:不想等待同步任务完成怎么办?
如果你的需求是让进程在main结束后直接退出,不管sync_sleep是否完成,可以自定义一个使用守护线程的executor:
import asyncio import time from concurrent.futures import ThreadPoolExecutor def sync_sleep(x): print("sync_sleep going to sleep") time.sleep(x) print("sync_sleep awake") async def main(): loop = asyncio.get_running_loop() # 创建使用守护线程的executor daemon_executor = ThreadPoolExecutor(thread_name_prefix="Daemon", daemon=True) loop.run_in_executor(daemon_executor, sync_sleep, 4) print("main going to sleep") await asyncio.sleep(1) print("main awake ") if __name__ == '__main__': loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) finally: loop.close() print("main finished")
这种情况下,主线程结束时会强制终止守护线程,进程会立即退出,sync_sleep awake大概率不会被打印(取决于线程是否来得及执行到那一步)。
内容的提问来源于stack exchange,提问作者adnanmuttaleb

