如何异步运行Discord.py机器人与定时消息异步函数?
问题原因与解决方案
错误原因
你碰到的RuntimeError是因为Discord.py的所有异步API操作(比如fetch_user、发送消息)必须在机器人自身的事件循环中执行。你之前用threading把机器人和定时提醒函数放到了两个独立的线程/事件循环里,跨循环调用机器人的异步方法时,aiohttp的超时上下文管理器无法正常工作,就会触发这个错误。
正确实现方式
不需要额外开启线程,直接利用Discord.py基于asyncio的特性,把定时任务作为后台任务加入到机器人的事件循环中即可。以下是完整的可运行代码:
import asyncio import discord from discord.ext import commands from datetime import datetime, timedelta DISCORD_BOT_TOKEN = "你的机器人令牌" DISCORD_ADMIN_ID = 你的管理员ID(数字) TARGET_HOUR = 14 # 下午2点 class MyBot(commands.Bot): def __init__(self): # 按需调整Intents,确保获取服务器和频道权限 super().__init__(command_prefix="!", intents=discord.Intents.all()) async def setup_hook(self): # 机器人启动完成后,启动定时提醒后台任务 self.loop.create_task(self.start_alerts()) async def start_alerts(self): await self.wait_until_ready() # 等待机器人完全登录、加载服务器信息 while not self.is_closed(): now = datetime.now() # 计算下一个下午2点的时间 target_time = now.replace(hour=TARGET_HOUR, minute=0, second=0, microsecond=0) if now >= target_time: # 如果今天已经过了2点,就设置为明天的2点 target_time += timedelta(days=1) # 计算需要等待的秒数,直接等待到目标时间,避免轮询浪费资源 wait_seconds = (target_time - now).total_seconds() await asyncio.sleep(wait_seconds) # 遍历所有服务器发送提醒 for guild in self.guilds: # 这里可以替换成你指定的频道ID,比如 guild.get_channel(频道ID) channel = guild.text_channels[0] if guild.text_channels else None if channel: try: await channel.send("下午2点的定时提醒来了!") except discord.Forbidden: print(f"服务器 {guild.name} 无发送消息权限") # 给管理员发送通知 try: admin = await self.fetch_user(DISCORD_ADMIN_ID) await admin.send("已完成所有服务器的定时提醒推送!") except discord.NotFound: print("未找到管理员用户") except discord.Forbidden: print("无法给管理员发送私信") if __name__ == "__main__": bot = MyBot() bot.run(DISCORD_BOT_TOKEN)
关键说明
- 共享事件循环:通过
self.loop.create_task把定时任务加入机器人的事件循环,和机器人的命令、事件处理共用同一个循环,避免跨线程/循环的API调用错误。 - 等待机器人就绪:
wait_until_ready()确保机器人完成登录、加载服务器列表后再执行任务,避免调用self.guilds时为空。 - 高效等待逻辑:直接计算距离下一个目标时间的秒数并
sleep,比每秒轮询更节省资源。
内容的提问来源于stack exchange,提问作者Aman Agarwal
相关产品推荐
相关产品推荐

