Tkinter GUI调用discord.py发送Discord消息出现协程复用报错
报错原因
RuntimeError: cannot reuse already awaited coroutine的触发点在按钮绑定代码:
bt_Send = tk.Button(text="Send", command=partial(client.loop.create_task, send()))
这行代码在按钮初始化阶段就执行了send(),生成了一个协程对象传给partial保存。第一次点击按钮时,事件循环把这个协程执行完毕,之后所有点击操作都会反复把这个已经执行过的旧协程丢给事件循环,协程对象本身不能重复await,就会抛出这个异常。
另外你把discord.py客户端放在子线程运行、Tkinter UI跑在主线程的架构下,直接在Tkinter主线程调用client.loop.create_task属于跨线程操作asyncio事件循环,本身就有线程安全隐患,会随机触发各种不可复现的异常。
修复方案
1. 修正按钮触发逻辑
不要在按钮初始化时提前生成协程对象,改成每次点击时临时生成新的协程,同时用asyncio提供的线程安全方法把协程投递到discord的事件循环执行:
import asyncio def on_send_click(): # 每次点击都新建全新的send协程,线程安全投递到discord事件循环 asyncio.run_coroutine_threadsafe(send(), client.loop) bt_Send = tk.Button(text="Send", command=on_send_click)
2. 优化send协程的鲁棒性
原send函数缺少边界判断,公会未加载、频道不存在时会直接抛属性错误,可以做简单优化:
async def send(): global en_message, en_ChannelName target_name = en_ChannelName.get().strip() # 输入为空直接返回 if not target_name: return guild = client.get_guild(some_guild_ID_you_want) # 公会未缓存时直接返回 if not guild: return for channel in guild.channels: # 用isinstance做类型判断更符合Python规范 if isinstance(channel, discord.TextChannel) and target_name.lower() in channel.name.lower(): await channel.send(en_message.get()) break
注意事项
discord.py的几乎所有API都不是线程安全的,后续所有和discord客户端交互的逻辑,都不要直接在Tkinter主线程调用,统一用asyncio.run_coroutine_threadsafe把逻辑封装成协程投递到客户端所在的事件循环线程执行,避免出现随机崩溃、数据错乱问题。
内容的提问来源于stack exchange,提问作者Pauel3312
相关产品推荐
相关产品推荐

