多线程环境下Discord Bot添加discord.ui.View触发RuntimeError求助
解决Discord Bot多线程中使用discord.ui.View的事件循环问题
问题根源
你遇到的RuntimeError: no running event loop,是因为discord.ui.View初始化时会调用asyncio.get_running_loop()获取当前线程的asyncio事件循环,但你的YOLO推理线程是普通同步线程,没有运行任何asyncio事件循环,因此触发错误。
解决方案
推荐采用将Discord交互逻辑(View创建+消息发送)移至主事件循环执行的方案,因为discord.py的API并非线程安全,所有与Discord的交互都应当在Bot的主事件循环线程中完成,工作线程仅负责处理阻塞的推理任务。
具体实现步骤
- 在Bot启动时保存主事件循环的引用
- 工作线程完成YOLO推理后,将创建View和发送消息的逻辑封装为协程,通过
asyncio.run_coroutine_threadsafe()提交到主事件循环执行
示例代码:
import asyncio import threading import discord from ultralytics import YOLO # 全局保存主事件循环引用 main_event_loop = None model = YOLO("yolov8n.pt") def start_bot(): global main_event_loop # 获取并保存主事件循环 main_event_loop = asyncio.get_running_loop() # 初始化Discord Bot bot = discord.Bot(intents=discord.Intents.all()) @bot.event async def on_ready(): print(f"Logged in as {bot.user}") @bot.event async def on_message(message): if message.author.bot: return # 处理带图片的消息,启动推理线程 if message.attachments: image_attachment = message.attachments[0] image_data = await image_attachment.read() threading.Thread(target=detect_task, args=(message, image_data)).start() bot.run("YOUR_BOT_TOKEN") def detect_task(message, image_data): # 执行YOLO推理(阻塞操作,放在线程中) results = model(image_data) result_content = f"检测到 {len(results)} 个目标" # 封装Discord交互逻辑为协程 async def send_result_with_view(): # 在主事件循环中创建View,此时能获取到运行的事件循环 view = discord.ui.View() view.add_item(discord.ui.Button(label="查看详情", style=discord.ButtonStyle.primary)) await message.channel.send(result_content, view=view) # 将协程提交到主事件循环执行 asyncio.run_coroutine_threadsafe(send_result_with_view(), main_event_loop) if __name__ == "__main__": start_bot()
不推荐的方案(仅作参考)
如果一定要在工作线程中处理View,可以为工作线程单独创建并运行asyncio事件循环,但这种方式可能引发discord.py对象的线程安全问题,增加代码复杂度:
def detect_task(message, image_data): results = model(image_data) result_content = f"检测到 {len(results)} 个目标" # 为工作线程创建新的事件循环 loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) async def send_with_view(): view = discord.ui.View() view.add_item(discord.ui.Button(label="查看详情", style=discord.ButtonStyle.primary)) # 注意:此方式可能引发discord对象线程安全问题,不推荐 await message.channel.send(result_content, view=view) loop.run_until_complete(send_with_view()) loop.close()
内容的提问来源于stack exchange,提问作者The one word assaulter
相关产品推荐
相关产品推荐

