# bot 生命周期 典型的 bot 程序的生命周期如下所示: ```{image} /_static/lifetime.svg :alt: lifetime :width: 300px :align: center ``` 1. 注入自定义机制:`import melobot` 发生时,会注入 melobot 自定义的导入机制与多进程生成机制,用于一些内部功能的实现。**多数情况不会对用户代码造成影响**。 2. 创建 bot:在实例化 {class}`.Bot` 对象后,即创建了 bot 对象(bot 实例)。 3. 添加源与适配器(或协议栈):这些组件添加后并不会立即启动,但它们已作为对象存在。 4. 添加插件:开始加载插件,并把插件中的功能扩展到 bot 实例上。 5. 启动 bot:开始运行各项功能。 6. bot 运行期:运行各类功能,执行各类协议组件与用户插件的功能。 6. 停止 bot:停止各项功能,并进行资源清理。 ## bot 启停阶段 启动 bot 的过程在时序上如图所示: ```{image} /_static/bot-start.svg :alt: lifetime :width: 450px :align: center ``` 为了简化逻辑,图中的调用关系为同步,实际上全是异步过程。 1. 简单来说,一个 bot 实例在启动时,会尝试异步地启动其上添加的所有适配器。 2. 每个适配器启动时,会尝试异步地启动同协议的所有源。 3. 同一协议的所有源,都启动完成并开始工作后,适配器也就启动完成并开始工作。 4. 如此递归直至所有源与适配器都启动完毕,bot 实例也就可以开始工作 停止 bot 的过程在时序上正好相反: 1. bot 实例的停止可以由 close 方法或终止信号(SIGINT,SIGTERM)触发 2. 停止时的顺序,正好与启动时相反,就像“栈”一样 假设有以下启动过程: ```text bot 启动 A 的适配器启动 A 的源 A1 启动 A1 开始工作 A 的适配器开始工作 B 的适配器启动 B 的源 B1 启动 B1 开始工作 B 的源 B2 启动 B2 开始工作 B 的适配器开始工作 bot 开始工作 ``` 那么停止时的顺序为: ``` bot 准备停止 B 的适配器准备停止 源 B2 准备停止 源 B2 结束工作 源 B1 准备停止 源 B1 结束工作 B 结束工作 A 的适配器准备停止 源 A1 准备停止 源 A1 结束工作 A 结束工作 bot 结束工作 ``` ```{admonition} 警告 :class: attention 大多数情况下,外部应该使用 bot 实例提供的接口,来顺便完成对适配器、源的启动。直接调用适配器、源的启停方法将发生未定义行为。 但不排除部分适配器或源有重启实现,这种情况下可以手动启停。不过具体用法要以文档说明为准。 ``` ## 生命周期钩子 bot,适配器,源对象都可以在指定的生命周期添加生命周期钩子。这一般也称作添加 hook。在对象抵达对应的生命周期结点后,便会执行你添加的钩子函数。 对于 bot 实例,有以下方法获取: ```python # 创建 bot 时可以拿到对象 bot = Bot(...) # 或者在插件中获取当前上下文的 bot from melobot.bot import get_bot bot = get_bot() # 当然依赖注入也是可以的 @on_xxx(...) async def _(bot: Bot) -> None: ... ``` 所有源对象和适配器对象都是可 hook 的。使用 {meth}`~.HookMixin.on` 方法,配合 {class}`.BotLifeSpan` 可以绑定一个 hook 函数: ```python async def f1() -> None: ... def f2() -> None: ... from melobot.bot import BotLifeSpan # 直接使用 bot.on(BotLifeSpan.LOADED)(f1) # 或取得装饰器 on_my_bot_loaded = bot.on(BotLifeSpan.LOADED) on_my_bot_loaded(f1) on_my_bot_loaded(f2) # 或直接使用装饰器语法 @bot.on(BotLifeSpan.LOADED) async def f3() -> None: ... ``` 各种 hook 类型的含义,参考 {class}`.BotLifeSpan` 的 API 文档。 实际上,bot 对象还有专属的语法糖: ```python # 使用属性,绑定在 LOADED 生命周期的 hook @bot.on_loaded async def _() -> None: ... # 其他 hook 类型类似 ``` 所有可 hook 对象,可以使用 {meth}`~melobot.mixin.HookMixin.get_hook_evoke_time` 获取最后一次触发 hook 的时间戳: ```python # 例如 bot 对象在任何时候都可以尝试: bot.get_hook_evoke_time(BotLifeSpan.STARTED) # 如果尚未抵达此生命周期,返回 -1 # 如果已抵达此生命周期,并触发过此 hook,会返回一个时间戳值 ``` 其他组件的生命周期钩子,会在后续章节穿插讲解。 ## 总结 本篇主要说明了 melobot 重要组件的生命周期与钩子。 下一篇将重点说明:源与适配器。