使用 asyncio 处理阻塞操作
编写 asyncio 代码时,必须确保所有阻塞操作都在独立线程中执行。如果在事件循环中发生阻塞操作,那么在它完成之前,其它任何任务都无法运行;换句话说,整个系统都会在阻塞期间停住。
下面会介绍常见的阻塞操作,例如网络 I/O 和高开销计算,以及对应的处理方式。
开发时请务必启用 asyncio 调试模式 和 Home Assistant 的调试模式。这样可以自动发现许多阻塞 I/O 的错误。
解决事件循环中的阻塞 I/O
如果你来到这个页面,很可能是因为 Home Assistant 检测到了事件循环中的阻塞调用并进行了报告。从 2024.7.0 开始,Home Assistant 可以检测更多类型的阻塞操作,以避免系统卡顿。这类错误可能导致系统无响应或出现未定义行为。下面是修复这类问题的一些建议。
在 executor 中运行阻塞调用
在 Home Assistant 中,通常使用 await hass.async_add_executor_job(...)。在库代码中,通常使用 await loop.run_in_executor(None, ...)。可参考 Python 文档中的运行阻塞代码,了解更多注意事项。某些具体调用可能还需要额外处理。
具体函数调用
根据检测到的阻塞调用类型,修复方式可能会更具体。
open
open 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
修复事件循环中的 open 调用时,也必须一并修复后续的阻塞读取和写入操作,确保它们也在 executor 中执行。Home Assistant 只能检测 open,无法检测阻塞的读写调用;如果只修复了 open 而遗漏后续读写,问题依然会存在,而且往往更难定位。
导入模块
sleep
阻塞式休眠应替换为 await asyncio.sleep(...)。事件循环中最常见的阻塞 sleep 问题来自 pyserial-asyncio;可以改用不包含该问题的 pyserial-asyncio-fast。
putrequest
urllib 确实会执行阻塞 I/O,应按上面的标准方式放到 executor 中运行。也可以考虑将集成迁移到 aiohttp 或 httpx。
glob.glob
glob.glob 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
glob.iglob
glob.iglob 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
os.walk
os.walk 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
os.listdir
os.listdir 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
os.scandir
os.scandir 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
os.stat
os.stat 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
pathlib.Path.write_bytes
pathlib.Path.write_bytes 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
pathlib.Path.write_text
pathlib.Path.write_text 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
pathlib.Path.read_bytes
pathlib.Path.read_bytes 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
pathlib.Path.read_text
pathlib.Path.read_text 会触发磁盘 I/O,应按上面的标准方式放到 executor 中运行。
SSLContext.load_default_certs
SSLContext.load_default_certs 会从磁盘加载证书,因此会产生磁盘 I/O。
下面这些 helper 可以确保相关阻塞 I/O 在 executor 中执行:
aiohttp:使用homeassistant.helpers.aiohttp_client.async_get_clientsession创建aiohttp.ClientSessionhttpx:使用homeassistant.helpers.httpx_client.get_async_client创建httpx.AsyncClient- 通用 SSL:使用
homeassistant.util.ssl
SSLContext.load_verify_locations
参见 SSLContext.load_default_certs。
SSLContext.load_cert_chain
参见 SSLContext.load_default_certs。

