Nextcord中如何终止已启动的wait_for消息等待?
问题
开发Discord斜杠命令时遇到以下问题:
- 功能为监听用户发送的图片并保存,基础功能正常
- 点击停止按钮后,若当前循环已进入
bot.wait_for("message")的等待阶段,仍会执行完本次循环迭代,无法立即终止等待
原核心等待代码:
check = lambda msg: interaction.user.id == msg.author.id message = await bot.wait_for("message", check=check)
原完整命令逻辑(不含装饰器):
async def auto(interaction:Interaction): await interaction.response.defer() event = asyncio.Event() embed = Embed(title="", color=0x2f3136) embed.add_field(name = "", value="Make a screenshot with `Win+Shift+S` and press `Ctrl+V` on the chat to send it.", inline= False) stop_button = Button(label="Stop", style = ButtonStyle.red) async def stopb_callback(interaction): event.set() stop_button.callback = stopb_callback view = View() view.add_item(stop_button) message_to_delete = await interaction.followup.send(embed=embed, view = view) while event.is_set() != True: print("Event:",event.is_set()) if event.is_set() != True: check = lambda msg: interaction.user.id == msg.author.id message = await bot.wait_for("message", check=check) if len(message.attachments) > 0: print(message.attachments[0].url) url = message.attachments[0].url #save image r = requests.get(url, stream=True) imageName = str(uuid.uuid4()) + '.png' with open(imageName, 'wb') as out_file: shutil.copyfileobj(r.raw, out_file) else: embed1 = Embed(title="", color=0x2f3136) embed1.add_field(name = "", value="You have to send a screenshot to start.", inline= False) message1 = await interaction.followup.send(embed=embed1) all_messages.append(message1)
尝试过变量控制循环和asyncio.Event,均无法解决等待阶段无法终止的问题。
解决方案
问题根源是bot.wait_for是阻塞式等待,一旦进入等待就无法被外部事件打断。可以通过asyncio.wait同时监听停止事件和消息事件,实现触发停止时立即中断等待。
具体修改点:
- 用
asyncio.wait同时启动两个任务:等待消息、等待停止事件被设置 - 检查哪个任务先完成:若停止事件触发则退出循环,否则处理消息
- 移除冗余的
event.is_set()判断,简化循环逻辑
修改后的完整代码:
import asyncio from discord import Interaction, Embed, Button, ButtonStyle, View import requests import shutil import uuid async def auto(interaction:Interaction): await interaction.response.defer() event = asyncio.Event() embed = Embed(title="", color=0x2f3136) embed.add_field(name = "", value="Make a screenshot with `Win+Shift+S` and press `Ctrl+V` on the chat to send it.", inline= False) stop_button = Button(label="Stop", style = ButtonStyle.red) async def stopb_callback(interaction): event.set() await interaction.response.defer() # 避免按钮无响应提示 stop_button.callback = stopb_callback view = View() view.add_item(stop_button) message_to_delete = await interaction.followup.send(embed=embed, view = view) check = lambda msg: interaction.user.id == msg.author.id while not event.is_set(): # 同时等待消息事件和停止事件,任一完成即返回 done, pending = await asyncio.wait( [ bot.wait_for("message", check=check), event.wait() ], return_when=asyncio.FIRST_COMPLETED ) # 检查是否是停止事件触发 if event.is_set(): # 取消未完成的任务(如果有的话) for task in pending: task.cancel() break # 处理收到的消息 message_task = next(iter(done)) message = message_task.result() if len(message.attachments) > 0: print(message.attachments[0].url) url = message.attachments[0].url # 保存图片 r = requests.get(url, stream=True) imageName = str(uuid.uuid4()) + '.png' with open(imageName, 'wb') as out_file: shutil.copyfileobj(r.raw, out_file) else: embed1 = Embed(title="", color=0x2f3136) embed1.add_field(name = "", value="You have to send a screenshot to start.", inline= False) message1 = await interaction.followup.send(embed=embed1) all_messages.append(message1) # 可选:停止后删除提示消息或更新状态 await message_to_delete.delete()
关键说明
asyncio.wait的return_when=FIRST_COMPLETED参数确保只要消息到达或停止事件触发,就立即返回结果- 停止事件触发时,需要取消未完成的
bot.wait_for任务,避免残留异步任务 - 给停止按钮的回调添加
await interaction.response.defer(),防止Discord提示按钮无响应
内容的提问来源于stack exchange,提问作者user18362633
相关产品推荐
相关产品推荐

