如何优雅捕获Ctrl+C信号并正常关闭Discord Py机器人
Discord.py机器人干净关机实现方案
核心问题说明
你写的bot.run()是同步阻塞方法,执行时会内部创建并运行事件循环,直到bot断开连接后才会退出,此时事件循环已经被关闭,无法直接在finally块中运行bot.close()这类协程对象。
推荐实现方案:手动管理事件循环
该方案支持完美捕获SIGINT/SIGTERM信号,按顺序执行自定义清理逻辑、卸载所有cog、关闭bot实例:
import asyncio import signal from discord.ext import commands # 初始化bot,按需求配置前缀和Intents bot = commands.Bot(command_prefix="!", intents=commands.Intents.all()) config = {"token": "你的机器人Token"} async def shutdown_handler(): """全局关机清理逻辑""" # 此处写入你的业务清理逻辑:比如更新数据库、关闭数据库连接、停止定时任务等 print("正在执行关机前资源清理...") # 关闭bot,会自动触发所有已加载cog的cog_unload钩子 await bot.close() def signal_callback(signal_num, frame): """信号触发回调,向运行中的事件循环添加关机任务""" asyncio.create_task(shutdown_handler()) async def main(): # 注册终止信号监听 running_loop = asyncio.get_running_loop() for sig in (signal.SIGINT, signal.SIGTERM): running_loop.add_signal_handler(sig, signal_callback, sig, None) # 异步启动bot async with bot: await bot.start(config["token"]) if __name__ == "__main__": asyncio.run(main())
Cog单独清理逻辑配置
你只需要在每个Cog类中实现cog_unload异步方法,bot关闭时会自动执行所有Cog的该方法,实现每个模块的独立清理:
from discord.ext import commands class CustomCog(commands.Cog): def __init__(self, bot): self.bot = bot # 例如该Cog持有独立的数据库连接、定时任务等资源 async def cog_unload(self): """Cog卸载时自动触发的钩子""" # 写入当前Cog专属的清理逻辑,比如关闭连接、取消任务 print(f"{self.__class__.__name__} 资源已清理完成") async def setup(bot): await bot.add_cog(CustomCog(bot))
原有Try/Finally方案兼容修改
如果你不想修改原有结构,也可以在finally块中手动创建临时事件循环执行协程:
import asyncio # 你的shutdown_handler改为异步方法 async def shutdown_handler(): # 清理逻辑 await bot.close() try: bot.run(config["token"]) finally: # 手动拉起事件循环执行协程 loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(shutdown_handler()) loop.close()
注意事项
- 不要重复调用
bot.close(),在全局清理逻辑中执行一次即可 - 如果使用Discord.py v2.x版本,需要正确配置Intents权限,否则bot无法正常启动
- 所有异步清理逻辑都需要放在异步函数内,通过事件循环调度执行
内容的提问来源于stack exchange,提问作者Jeremy Hsieh
相关产品推荐
相关产品推荐

