For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt, and this page is available as Markdown at /developers/asyncio_thread_safety.md.

Asyncio 中的线程安全

使用 asyncio 开发需要仔细关注线程安全,因为几乎所有 asyncio 对象都不是线程安全的。如果你刚刚开始使用 asyncio,请查阅 Python 关于 使用 asyncio 开发 的文档,以获取避免陷阱的提示。

Home Assistant 有一些在同一个代码库中处理 async 和非 async 代码的约定。主要要点如下:

  • 从 helper 调用函数时如何运行取决于它是否使用了 @callback 修饰,以表明它不会阻塞且在 event loop 中运行是安全的;更多详情请参见 处理 Async
  • 大多数 API 在从线程调用函数时都有同步和异步版本。async API 以 async_ 为前缀。例如,在从 event loop 之外的线程触发 event 时,使用 hass.bus.fire 而不是 hass.bus.async_fire
Tip

开发期间务必启用 asyncio debug modeHome Assistant 内置的 debug mode,因为许多线程安全错误可以自动检测。

解决线程安全错误

你可能到达此页面是因为 Home Assistant 检测到并报告了线程安全错误。从版本 2024.5.0 开始,Home Assistant 可以检测、报告并阻止一些非线程安全操作,以防止系统不稳定。在 Home Assistant 能够检测这些错误之前,它们可能导致意外重启或未定义行为,因为它们会破坏内部 asyncio 状态。以下是纠正非线程操作的一些提示。

确保代码在正确的线程中运行

接受 callback 的内置 helper

使用 Home Assistant 的内置 helper(如 event.async_track_state_change_eventevent.track_state_change_event)时,重要的是根据代码运行在哪个线程来调用正确的 API。如果代码运行在 event loop 之外的线程中,请使用非 async 版本。

在下面的示例中,所有内容都将在 event loop 线程中运行,当 async_track_state_change_event 触发时,async_update_event_state_callback 也将在 event loop 线程中运行,因为它使用了 @callback 修饰。如果缺少 @callback 修饰器,async_update_event_state_callback 将在 executor 中运行,从而对 async_write_ha_state 进行非线程安全调用。


    async def async_added_to_hass(self) -> None:
        """Entity 已被添加到 hass。"""
        self.async_on_remove(
            async_track_state_change_event(
                self.hass,
                ["light.other"],
                self.async_update_event_state_callback,
            )
        )

    @callback
    def async_update_event_state_callback(self, event: Event[EventStateChangedData]) -> None:
        """Entity 状态更改时调用。"""
        new_state = event.data["new_state"]
        if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
            return
        self.async_write_ha_state()

特定 API 调用

你可能会发现需要从 event loop 线程之外的线程调用其中一个 async API 调用。在大多数情况下,hass.add_job 可以安全地从其他线程调用 async API。一些 helper 有特定的同步 API 用于从其他线程调用。下面是常用的 async API 及其从其他线程调用的方法的列表。

hass.async_create_task

在从 event loop 线程之外的线程创建任务时,请改用 hass.create_task

hass.bus.async_fire

在从 event loop 线程之外的线程触发 event 时,请改用 hass.bus.fire

hass.services.async_register

在从 event loop 线程之外的线程注册 service action 时,请改用 hass.services.register

hass.services.async_remove

在从 event loop 线程之外的线程移除 service action 时,请改用 hass.services.remove

async_write_ha_state

在从 event loop 线程之外的线程写入 entity 状态时,请改用 self.schedule_update_ha_state

hass.config_entries.async_update_entry

更新 config entry 必须在 event loop 线程中完成。没有同步的 API 来更新 config entry。如果调用函数在另一个线程中运行不是错误,请使用 hass.add_job 在 event loop 中调度一个调用 hass.config_entries.async_update_entry 的函数。

async_dispatcher_send

在从 event loop 线程之外的线程调用 dispatcher 时,请改用 dispatcher_send

async_render_to_info

模板必须在 event loop 线程中渲染。没有同步的 API 来渲染模板。使用 hass.add_job 在 event loop 中调度一个调用 async_render_to_info 的函数。

area_registry.async_create

区域注册表必须在 event loop 线程中修改。区域注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 area_registry.async_create 的函数。

area_registry.async_delete

区域注册表必须在 event loop 线程中修改。区域注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 area_registry.async_delete 的函数。

area_registry.async_update

区域注册表必须在 event loop 线程中修改。区域注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 area_registry.async_update 的函数。

category_registry.async_create

分类注册表必须在 event loop 线程中修改。分类注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 category_registry.async_create 的函数。

category_registry.async_delete

分类注册表必须在 event loop 线程中修改。分类注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 category_registry.async_delete 的函数。

category_registry.async_update

分类注册表必须在 event loop 线程中修改。分类注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 category_registry.async_update 的函数。

device_registry.async_update_device

设备注册表必须在 event loop 线程中修改。设备注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 device_registry.async_update_device 的函数。

device_registry.async_remove_device

设备注册表必须在 event loop 线程中修改。设备注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 device_registry.async_remove_device 的函数。

entity_registry.async_get_or_create

Entity 注册表必须在 event loop 线程中修改。Entity 注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 entity_registry.async_get_or_create 的函数。

entity_registry.async_remove

Entity 注册表必须在 event loop 线程中修改。Entity 注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 entity_registry.async_remove 的函数。

entity_registry.async_update_entity

Entity 注册表必须在 event loop 线程中修改。Entity 注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 entity_registry.async_update_entity 的函数。

floor_registry.async_create

楼层注册表必须在 event loop 线程中修改。楼层注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 floor_registry.async_create 的函数。

floor_registry.async_delete

楼层注册表必须在 event loop 线程中修改。楼层注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 floor_registry.async_delete 的函数。

floor_registry.async_update

楼层注册表必须在 event loop 线程中修改。楼层注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 floor_registry.async_update 的函数。

issue_registry.async_get_or_create

问题注册表必须在 event loop 线程中修改。请改用 issue_registry.create_issue

issue_registry.async_delete

问题注册表必须在 event loop 线程中修改。请改用 issue_registry.delete_issue

issue_registry.async_ignore

问题注册表必须在 event loop 线程中修改。问题注册表没有同步的 API 来忽略 issue。使用 hass.add_job 在 event loop 中调度一个调用 issue_registry.async_ignore_issue 的函数。

label_registry.async_create

标签注册表必须在 event loop 线程中修改。标签注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 label_registry.async_create 的函数。

label_registry.async_delete

标签注册表必须在 event loop 线程中修改。标签注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 label_registry.async_delete 的函数。

label_registry.async_update

标签注册表必须在 event loop 线程中修改。标签注册表没有同步的 API。使用 hass.add_job 在 event loop 中调度一个调用 label_registry.async_update 的函数。