函数分类
在 Home Assistant 中,工作是通过函数来表示的。函数究竟运行在事件循环中,还是在线程池中执行,取决于它是否适合在异步环境中安全运行。
Home Assistant 约定:所有必须在事件循环中运行的函数,都以 async_ 为前缀。
协程函数
协程是基于 Python 生成器语法扩展出来的一类特殊函数,允许它们在等待结果时暂停执行。
调用协程函数会返回一个 coroutine 对象,但不会立刻开始执行。只有当这个对象被 await,或者被调度到事件循环上之后,函数才会真正运行。
要把一个函数声明为协程,只需在 def 前加上 async。
在这个示例中,我们通过调用 hass.loop.create_task 来调度协程。这样会把协程加入待运行任务队列。当事件循环运行 async_look_my_coroutine 时,它会在执行 await entity.async_turn_on() 时挂起。此时会调度新的任务去执行 entity.async_turn_on();等那个任务完成后,async_look_my_coroutine 会继续执行。
回调函数
回调函数是普通函数,但被认为可以安全地在事件循环中直接运行。回调不能自行挂起,不能执行 I/O,也不能直接等待协程结果。它可以调度新的任务,但不能 await。
要将函数标记为回调,请从 Core 导入 callback 装饰器并应用到函数上。
Home Assistant 中的典型场景,是将 callback 作为事件监听器或服务处理器。它可以先处理收到的数据,再调度真正的异步操作。自动化引擎就是一个例子。
在这个例子中,entity.async_trigger 是一个协程函数。调用它会返回一个 coroutine 对象,并在调度后使用给定参数执行。
为了真正执行这个任务,我们必须把它调度到事件循环中,也就是调用 hass.loop.create_task。
为什么还需要回调?
你可能会问:既然协程能完成回调能做的一切,为什么还需要回调?原因在于性能和状态一致性。
当协程 A 等待协程 B 时,A 会挂起自己,并安排新的任务去运行 B。这意味着事件循环会运行 A、再运行 B、然后再回到 A。如果 B 是 callback,那么 A 就不需要挂起,事件循环只需连续运行 A。这样可以减少任务切换,也能让状态变化更连贯;否则,其他事件可能会插入到两次协程执行之间。
可在线程和事件循环中安全运行的函数
这类函数既可以在线程中运行,也可以在事件循环中运行。它们通常只在内存中做计算或转换数据,不涉及任何 I/O。很多标准库函数都属于这一类,例如用 sum 汇总一组数字,或合并两个字典。
这类函数没有专门的装饰器标记;如果打算在事件循环中调用它们,需要自行确认实现是否安全。不确定时,请查看源码。
其他函数
凡是不属于以上几类的函数,都归为这一类。它们要么是线程安全的同步函数,要么不适合在事件循环中运行;典型例子是包含 sleep 或执行 I/O 的函数。
这类函数也不需要特殊标记。

