
本文详解如何为 Pyrogram 的异步事件处理器(如 @app.on_message)设计兼容 asyncio.run 的装饰器,避免直接在装饰器中调用 asyncio.run 导致的 RuntimeError,并提供可复用的高阶装饰器模式与最佳实践。
本文详解如何为 pyrogram 的异步事件处理器(如 `@app.on_message`)设计兼容 `asyncio.run` 的装饰器,避免直接在装饰器中调用 `asyncio.run` 导致的 runtimeerror,并提供可复用的高阶装饰器模式与最佳实践。
在 Pyrogram 开发中,常见误区是试图将 asyncio.run() 直接用于装饰器内部或错误地包裹事件处理函数(如 @app.on_message),这会导致 RuntimeError: asyncio.run() cannot be called from a running event loop —— 因为 Pyrogram 自身已在运行一个事件循环,重复启动会冲突。
正确的做法是:装饰器本身不启动事件循环,仅包装协程逻辑;真正的 asyncio.run() 仅用于启动主应用(如 app.start() 或独立脚本入口),而非装饰后的处理函数。
以下是一个专业、安全、可复用的异步装饰器实现:
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] Exiting {func.__name__} successfully")
return result
except Exception as e:
print(f"[ERROR] {func.__name__} raised {type(e).__name__}: {e}")
raise
return wrapper
# ✅ 正确用法:装饰 Pyrogram 处理器(它本身由 Pyrogram 的 event loop 调用)
@app.on_message(filters.text)
@log_execution
async def reply_on_message(client, message):
await message.reply("Hello! Decorator is working ✅")
# ✅ 主程序入口:Pyrogram 推荐方式 —— 使用 app.run() 启动(内部已封装 asyncio.run)
if __name__ == "__main__":
app.run() # ✅ 推荐:Pyrogram 自动管理事件循环⚠️ 关键注意事项:
- ❌ 不要写 app.run(main()):main() 是协程对象,传入后会被立即调用并返回 None,且 app.run() 期望接收无参可调用对象或直接启动客户端。
- ❌ 避免在装饰器内调用 asyncio.run():这会尝试嵌套启动新事件循环,必然失败。
- ✅ 装饰器应返回 async def wrapper(...),保持协程本质,交由外部事件循环调度。
- ✅ 若需独立测试装饰器,可在模块级用 asyncio.run(my_decorated_func()) —— 但生产环境必须交由 Pyrogram 统一调度。
总结:异步装饰器的本质是“增强协程行为”,而非“接管执行”。它应透明兼容任何事件循环环境(包括 Pyrogram、FastAPI 或纯 asyncio.run 场景)。遵循此范式,即可安全扩展日志、重试、权限校验等横切逻辑,同时保障框架稳定性。

















