Discord机器人如何高效实现多用户并行可终止定时提醒功能
解决方案
原有代码核心问题
- 用了阻塞的
time.sleep(),会直接卡住discord.py的整个异步事件循环,导致所有事件处理都变慢 - 在
on_message回调里直接写while死循环,会让该协程一直占用资源不释放,用户量上来后资源直接耗尽 - 用列表存储运行中用户的ID,查询、删除的时间复杂度都是O(n),用户多了效率极低
最优实现方案
用asyncio.Task管理每个用户的独立定时任务,是discord.py生态下处理多定时任务的标准方案,支持上千并发完全无压力。完整实现代码如下:
import asyncio from datetime import datetime, timedelta import discord # key为用户ID,value为对应的定时任务Task对象 user_timers = {} NOTIFY_CHANNEL_ID = 877932875566153789 prefix = "!" # 替换为自己的命令前缀 # 独立的用户定时提醒协程 async def timer_worker(user_id: int, channel): try: while True: # 非阻塞等待3小时,不会卡住事件循环 await asyncio.sleep(3 * 3600) await channel.send(f"<@{user_id}> Your timer is running!") except asyncio.CancelledError: # 任务被取消时自动清理 if user_id in user_timers: del user_timers[user_id] raise @client.event async def on_message(message): if message.author == client.user: return command = message.content.split(' ') # 启动定时器逻辑 if command[0] == (f'{prefix}start'): try: # 原有获取时区、计算本地时间逻辑保留 timezone = timezoneDatabase[str(message.author.id)][0].split(':') hours = int(timezone[0]) minutes = int(timezone[1]) utcTime = datetime.utcnow() hours_added = timedelta(hours=hours, minutes=minutes) calculated_time = (utcTime + hours_added).strftime("%Y-%m-%d %H:%M:%S") # 校验用户是否已启动定时器 if data.getStartTime(str(message.author)) != False: embed = discord.Embed( title='Timer started', description=f'<@{message.author.id}> Your timer has already started!', color=0x8f6c54 ) await message.channel.send(embed=embed) return if data.logStartTime(message.author, calculated_time) != 'ok': # 谷歌表写入失败的自定义异常处理可以放在这里 return # 创建并存储用户专属定时任务 channel = client.get_channel(NOTIFY_CHANNEL_ID) timer_task = asyncio.create_task(timer_worker(message.author.id, channel)) user_timers[message.author.id] = timer_task embed = discord.Embed( title='Timer started', description=f'<@{message.author.id}> Your timer has started counting!', color=0x8f6c54 ) await message.channel.send(embed=embed) except Exception as e: # 按需添加异常处理 pass # 停止定时器逻辑 if command[0] == f'{prefix}stop': user_id = message.author.id if user_id not in user_timers: embed = discord.Embed( title='Timer not found', description=f'<@{user_id}> You don\'t have a running timer!', color=0x8f6c54 ) await message.channel.send(embed=embed) return # 取消定时任务 user_timers[user_id].cancel() # 更新谷歌表状态 data.clearStartTime(message.author) embed = discord.Embed( title='Timer stopped', description=f'<@{user_id}> Your timer has been stopped!', color=0x8f6c54 ) await message.channel.send(embed=embed)
补充优化点
- 机器人重启时可以遍历谷歌表里所有未停止的定时器,根据启动时间计算下一次提醒的剩余时间,重新创建Task加入
user_timers,避免重启丢失所有定时任务 - 如果需要更精准的定时(比如避免asyncio.sleep的误差),可以每次计算下一次提醒的绝对时间,用
await asyncio.sleep((next_run_time - datetime.utcnow()).total_seconds())替代固定3小时等待 - 可以给Task加done回调,处理异常退出的情况,避免用户定时器莫名挂掉没有通知
内容的提问来源于stack exchange,提问作者layz
相关产品推荐
相关产品推荐

