asyncio 的线程安全
使用 asyncio 开发时必须特别注意线程安全,因为几乎所有 asyncio 对象都不是线程安全的。如果你刚开始接触 asyncio,建议先阅读 Python 文档中的使用 asyncio 开发,以避开常见陷阱。
在同一个代码库里同时处理异步和非异步代码时,Home Assistant 有一套约定。最重要的是:
- 是否能直接在事件循环中运行某个函数,取决于它是否用
@callback装饰,以明确表明它不会阻塞,可以安全地在事件循环线程中执行;详见使用异步。 - 从线程调用函数时,大多数 API 同时提供同步版和异步版。异步 API 一般以前缀
async_命名。例如,从事件循环以外的线程触发事件时,应使用hass.bus.fire,而不是hass.bus.async_fire。
开发时请务必启用 asyncio 调试模式 和 Home Assistant 的调试模式。这样可以自动发现许多线程安全错误。
解决线程安全错误
如果你来到这个页面,很可能是因为 Home Assistant 检测并报告了线程安全问题。从 2024.5.0 开始,Home Assistant 可以检测、报告并阻止一部分非线程安全操作,以避免系统卡死。这类错误可能导致意外重启或未定义行为,因为它们可能破坏 asyncio 的内部状态。下面是一些修复建议。
确保代码运行在正确的线程中
接收 callback 的内置 helper
使用 Home Assistant 的内置 helper(例如 event.async_track_state_change_event 或 event.track_state_change_event)时,必须根据代码所在的线程选择正确的 API。如果代码运行在事件循环线程中,请使用异步版本。
在下面的示例中,所有代码都运行在事件循环线程中。当 async_track_state_change_event 触发时,async_update_event_state_callback 也会在事件循环线程中执行,因为它使用了 @callback 装饰器。如果缺少 @callback,async_update_event_state_callback 会被放到 executor 中运行,进而导致对 async_write_ha_state 的非线程安全调用。
具体 API 调用
你可能需要从事件循环线程之外调用某个异步 API。在大多数情况下,可以使用 hass.add_job 安全地从其他线程调度异步调用。有些 helper 还提供了专门的同步 API。下面列出的是最常见的情况。
hass.async_create_task
如果是在事件循环线程之外创建任务,请改用 hass.create_task。
hass.bus.async_fire
如果是在事件循环线程之外触发事件,请改用 hass.bus.fire。
hass.services.async_register
如果是在事件循环线程之外注册服务,请改用 hass.services.register。
hass.services.async_remove
如果是在事件循环线程之外移除服务,请改用 hass.services.remove。
async_write_ha_state
如果是在事件循环线程之外更新实体状态,请改用 self.schedule_update_ha_state。
hass.config_entries.async_update_entry
更新配置条目必须在事件循环线程中完成。这个操作没有同步 API。如果调用方在其它线程中运行,可使用 hass.add_job 在事件循环中调度调用 hass.config_entries.async_update_entry 的函数。
async_dispatcher_send
如果是在事件循环线程之外调用 dispatcher,请改用 dispatcher_send。
async_render_to_info
模板必须在事件循环线程中渲染。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 async_render_to_info 的函数。
area_registry.async_create
对 area registry 的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 area_registry.async_create 的函数。
area_registry.async_delete
对 area registry 的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 area_registry.async_delete 的函数。
area_registry.async_update
对 area registry 的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 area_registry.async_update 的函数。
category_registry.async_create
对 category registry 的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 category_registry.async_create 的函数。
category_registry.async_delete
对 category registry 的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 category_registry.async_delete 的函数。
category_registry.async_update
对 category registry 的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 category_registry.async_update 的函数。
device_registry.async_update_device
对设备注册表的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 device_registry.async_update_device 的函数。
device_registry.async_remove_device
对设备注册表的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 device_registry.async_remove_device 的函数。
entity_registry.async_get_or_create
对实体注册表的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 entity_registry.async_get_or_create 的函数。
entity_registry.async_remove
对实体注册表的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事件循环中调度调用 entity_registry.async_remove 的函数。
entity_registry.async_update_entity
对实体注册表的修改必须在事件循环线程中完成。这个操作没有同步 API。请使用 hass.add_job 在事

