如何正确将同步函数转为异步函数?Telegram Bot遇序列化问题
问题解决:ProcessPoolExecutor 序列化函数报错的修复方案
错误原因
你遇到的PicklingError是因为:装饰器将原同步函数sync_func替换成了包装后的异步函数,而包装函数内部引用的原未装饰的sync_func,在子进程的__main__模块中不存在(子进程启动时导入__main__,此时sync_func是被装饰后的异步函数)。pickle序列化时会校验函数的身份一致性,发现主进程和子进程中的sync_func不是同一个对象,因此报错。
修复方案
方案1:调整装饰器实现+分离同步函数到单独模块
把耗时的同步逻辑放到单独的模块中(避免主进程和子进程的函数身份冲突),同时修改装饰器,不使用partial打包函数,直接传递函数和参数到进程池:
- 创建
tasks.py文件,存放同步逻辑:
# tasks.py def sync_process_video(a): # 这里替换成你的视频下载/压缩逻辑 import time time.sleep(10) return f"处理完成:{a}"
- 主文件代码:
import asyncio from concurrent.futures import ProcessPoolExecutor from functools import wraps from tasks import sync_process_video executor = ProcessPoolExecutor() def async_wrap(func): @wraps(func) async def run(*args, **kwargs): loop = asyncio.get_running_loop() # 直接传递函数和参数,避免partial打包导致的序列化问题 return await loop.run_in_executor(executor, func, *args, **kwargs) return run # 装饰同步函数 async_process_video = async_wrap(sync_process_video) async def main(): result = await async_process_video(4) print(result) if __name__ == "__main__": asyncio.run(main())
方案2:去掉装饰器,直接调用进程池(更简洁)
不需要装饰器,直接在异步函数中通过loop.run_in_executor提交同步任务,这是更简单的实现方式:
import asyncio from concurrent.futures import ProcessPoolExecutor from tasks import sync_process_video executor = ProcessPoolExecutor() async def main(): loop = asyncio.get_running_loop() # 直接提交同步任务到进程池,不阻塞事件循环 result = await loop.run_in_executor(executor, sync_process_video, 4) print(result) if __name__ == "__main__": asyncio.run(main())
适配Telegram Bot的建议
在你的Telegram Bot开发中,推荐使用这种方式:
- 把视频下载、压缩等耗时逻辑封装成独立的同步函数,放在单独模块中
- 在Bot的异步消息处理函数中,用
loop.run_in_executor将耗时任务提交到进程池 - 这样Bot的事件循环不会被阻塞,能同时响应新的用户请求
内容的提问来源于stack exchange,提问作者Mikhail
相关产品推荐
相关产品推荐

