,

python异步编程二

1. 不要在协程中调用同步阻塞函数 2. 始终使用 asyncio.run 作为入口 3. 小心协程对象未被 …

1. 不要在协程中调用同步阻塞函数

import time


async def bad():
   time.sleep(1)  # 错误!将阻塞整个事件循环


async def good():
   await asyncio.sleep(1)  # 正确!主动让出控制权


async def acceptable():
   await asyncio.to_thread(time.sleep, 1)  # 可行!让线程池去阻塞

2. 始终使用 asyncio.run 作为入口

# 错误:手动管理事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
task = loop.create_task(main())
loop.run_forever()

# 正确:asyncio.run 自动创建和关闭事件循环
asyncio.run(main())

3. 小心协程对象未被 await

async def main():
   # 错误:创建了协程但未 await,协程永远不会执行
   fetch("url", 1)

   # 正确
   await fetch("url", 1)

   # 或通过 gather
   await asyncio.gather(fetch("url1", 1), fetch("url2", 2))

4. gather 异常处理

gather 默认任何协程异常都会立即传播,其他协程不会取消,但结果丢失

async def main():
   # 方式一:使用 return_exceptions=True
   results = await asyncio.gather(
       risky_task(),
       safe_task(),
       return_exceptions=True,
  )
   for r in results:
       if isinstance(r, Exception):
           print(f"某个任务失败: {r}")

   # 方式二:使用 TaskGroup(Python 3.11+)
   # 任一异常会取消组内所有任务

5. 使用 debug 模式

asyncio 的 debug 模式可以帮助你发现异步代码中的常见问题,比如协程阻塞事件循环、忘记 await、回调执行时间过长等。

# 开启 asyncio 调试模式
asyncio.run(main(), debug=True)

# 或通过环境变量
# PYTHONASYNCIODEBUG=1 python script.py

检测长时间阻塞的协程

debug 模式下,事件循环会监控每个协程的执行时间。如果某个协程执行超过 0.1 秒(默认阈值),会在 stderr 输出警告:

import time
import asyncio

async def blocking_coroutine():
   """模拟一个协程内部做了同步阻塞操作"""
   print("开始阻塞操作...")
   time.sleep(0.2)  # 同步阻塞,会阻塞整个事件循环
   print("阻塞操作结束")


async def main():
   await blocking_coroutine()


asyncio.run(main(), debug=True)

输出类似:

开始阻塞操作...
阻塞操作结束
Executor <TaskInfo name='Task-1' ...> running at (...)
  blocking_coroutine at demo.py:12
  main at demo.py:18
  ...

time.sleep(0.2) 是同步阻塞,但 debug 模式下检测到协程在同一个位置停留超过 0.1 秒,会打印出执行栈信息,精确定位阻塞的代码行。

检测未 await 的协程对象

忘记 await 协程是新手最容易犯的错误,debug 模式会检测到协程对象被创建但从未被迭代:

import asyncio


async def fetch_data(url: str) -> str:
   await asyncio.sleep(0.5)
   return f"{url} 数据"


async def main():
   # 忘记 await,协程对象永远不会执行
   fetch_data("https://example.com")

   await asyncio.sleep(1)


asyncio.run(main(), debug=True)

输出类似:

Coroutine 'fetch_data' was never awaited (at demo.py:12)

这个警告在你忘记 await 时非常有用,避免协程”静默丢失”。

自定义慢操作阈值

通过 loop.slow_callback_duration 调整检测阈值:

async def main():
   loop = asyncio.get_running_loop()
   loop.slow_callback_duration = 0.5  # 改为 0.5 秒才报警

   def acceptable_callback():
       time.sleep(0.3)  # 0.3 秒,低于自定义阈值,不会报警

   loop.call_later(0.1, acceptable_callback)
   await asyncio.sleep(0.5)


asyncio.run(main(), debug=True)

6. 避免全局事件循环

# 错误:在模块级别获取事件循环
loop = asyncio.get_event_loop()  # 可能获取到错误的事件循环

# 正确:在协程内部获取当前事件循环
async def my_func():
   loop = asyncio.get_running_loop()

7. CancelledError 的正确处理

async def cleanup():
   try:
       await long_running_task()
   except asyncio.CancelledError:
       # 务必完成清理后再重新抛出
       await release_resources()
       raise  # 必须重新抛出

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

About the Author

每个人都有自己得时区,在自己得时区里,一切都是准时的。

BlockSpare — News, Magazine and Blog Addons for (Gutenberg) Block Editor