必须用loop.run_in_executor将阻塞操作移交线程池或进程池执行:i/o选threadpoolexecutor,cpu密集型选processpoolexecutor;须await等待结果;不可在executor中执行异步代码;自定义executor需手动shutdown;优先用none交由默认线程池。

如果您在 Python 异步程序中需要执行阻塞型 I/O 或 CPU 密集型操作,但又不希望阻塞事件循环,则必须借助 loop.run_in_executor 将其移交至线程池或进程池执行。以下是正确使用该方法的关键步骤和常见实践:
一、明确 executor 类型并合理选择
run_in_executor 支持传入自定义的 concurrent.futures.Executor 实例,包括 ThreadPoolExecutor 和 ProcessPoolExecutor。对于 I/O 阻塞操作(如文件读写、数据库查询),应优先使用线程池;对于 CPU 密集型任务(如数值计算、图像处理),应使用进程池以规避 GIL 限制。
1、导入所需模块:from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
2、创建线程池实例:thread_pool = ThreadPoolExecutor(max_workers=4)
立即学习“Python免费学习笔记(深入)”;
3、创建进程池实例:process_pool = ProcessPoolExecutor(max_workers=2)
4、调用时显式传入:await loop.run_in_executor(thread_pool, blocking_func, *args)
二、始终使用 await 并避免直接调用返回值
run_in_executor 返回一个 Awaitable 对象,而非立即执行结果。若未用 await 等待,将导致协程未完成即继续执行后续逻辑,引发结果丢失或竞态问题。
1、正确写法:result = await loop.run_in_executor(None, time.sleep, 1)
2、错误写法:loop.run_in_executor(None, time.sleep, 1)(未 await,返回协程对象但被丢弃)
3、错误写法:result = loop.run_in_executor(None, time.sleep, 1)(赋值的是协程对象,非实际返回值)
三、避免在 executor 中执行异步代码
线程池或进程池中的函数运行于普通同步上下文,无法识别 async def 函数或 await 表达式。若将协程对象传入 executor,会直接抛出 TypeError,且不会触发任何异步调度。
1、禁止传入协程函数:await loop.run_in_executor(None, async_func)
2、禁止在 executor 内部使用 await:def bad_worker(): await asyncio.sleep(1)
3、正确做法:将异步逻辑保留在主事件循环中,仅把纯同步函数移入 executor
四、手动管理 executor 生命周期防止资源泄漏
若使用自定义 executor(非传入 None),必须确保其在不再需要时被显式关闭,否则线程/进程将持续驻留,造成资源占用和难以调试的内存泄漏。
1、使用 try/finally 块包裹 executor 使用区域
2、在 finally 中调用 executor.shutdown(wait=True)
3、关键提示:未调用 shutdown 的自定义 executor 不会自动终止后台线程
五、优先使用 None 参数委托给默认线程池
当未传入 executor 时,run_in_executor 会使用事件循环内置的默认线程池(asyncio.DefaultExecutor)。该池由事件循环统一管理,无需手动关闭,适合大多数轻量级 I/O 操作场景。
1、启用默认线程池:await loop.run_in_executor(None, os.listdir, "/tmp")
2、注意:默认线程池最大线程数为 min(32, os.cpu_count() + 4),不可配置
3、适用于短时、低频、非关键路径的阻塞调用,避免长期占用默认池资源










