
本文详解如何在 Pyrogram 等异步框架中安全、规范地将自定义装饰器应用于异步处理函数,并避免 asyncio.run() 被误用于已由事件循环托管的协程(如 @app.on_message),同时提供可复用的装饰器封装方案。
本文详解如何在 pyrogram 等异步框架中安全、规范地将自定义装饰器应用于异步处理函数,并避免 `asyncio.run()` 被误用于已由事件循环托管的协程(如 `@app.on_message`),同时提供可复用的装饰器封装方案。
在 Pyrogram(或类似异步框架如 Telethon、aiohttp)中,常见误区是试图对已被框架内部事件循环调度的异步处理器(如 @app.on_message)手动调用 asyncio.run() —— 这会导致 RuntimeError: asyncio.run() cannot be called from a running event loop。Pyrogram 的 app.run() 本身已启动并管理全局事件循环,所有装饰器修饰的处理器函数(如消息响应函数)均由该循环自动调度执行,绝不可在其内部或外部再嵌套 asyncio.run()。
✅ 正确做法:装饰器应返回一个协程包装器(coroutine wrapper),保持函数仍为 async def,以便被事件循环原生调用。以下是一个生产就绪的装饰器示例:
import asyncio
from functools import wraps
def log_execution(func):
"""异步装饰器:记录函数执行前后日志"""
@wraps(func) # 保留原函数元信息
async def wrapper(*args, **kwargs):
print(f"[INFO] Entering {func.__name__}")
try:
result = await func(*args, **kwargs)
print(f"[INFO] Completed {func.__name__}")
return result
except Exception as e:
print(f"[ERROR] {func.__name__} failed: {e}")
raise
return wrapper
# ✅ 正确:装饰器作用于 Pyrogram 消息处理器
@app.on_message()
@log_execution
async def reply_on_message(client, message):
await message.reply("Hello from decorated handler!")
⚠️ 关键注意事项:
- 不要在 @app.on_message 函数内或其调用链中使用 asyncio.run() —— Pyrogram 已托管事件循环;
- app.run(main()) 是错误写法:main() 是协程对象,不应直接传入;若需启动后台任务,应使用 app.start() + asyncio.create_task();
- 若需长期运行的后台协程(如轮询或定时任务),应在 app.start() 后显式创建任务:
async def background_task():
while True:
print("Running background job...")
await asyncio.sleep(10)
if __name__ == "__main__":
app.start()
asyncio.create_task(background_task()) # 非阻塞启动后台任务
app idle() # 保持主事件循环运行
总结:异步装饰器的本质是「协程到协程」的增强,而非同步化封装;其设计必须兼容事件循环调度。掌握 @wraps、await func() 和任务生命周期管理,是构建健壮 Pyrogram 应用的基础。











