Python3.9使用异步MySQL驱动出现Task was destroyed but it is pending!报错如何解决?
报错含义、触发原因与修复方案
报错含义
Task was destroyed but it is pending!是Python asyncio事件循环抛出的底层异常:指代一个处于待调度/运行中状态的异步任务,还未执行完成就被垃圾回收机制提前销毁。对应你的场景,就是discord框架生成的on_message消息监听任务未完成执行就被回收,因此不会继续处理后续的消息响应、计数逻辑。
触发原因
结合你切换异步MySQL驱动、Python3.9的背景,以及代码特征,核心触发点如下:
- MySQL连接关闭逻辑未加await:你代码中所有的
mydb.close()都没有前缀await,异步驱动的close本身是协程对象,直接调用不会等待连接资源回收完成,会残留未完成的IO任务,当MySQL连接池复用或销毁连接时,会连带持有该连接的on_message任务上下文被意外释放,导致任务被提前回收。 - 全局冷却列表存在异步竞态:你使用全局普通列表
member_cooldown_list存储冷却信息,高并发场景下多个on_message任务同时修改该列表会出现竞态条件,极端情况会导致任务上下文引用计数异常,被垃圾回收误判为无引用对象而销毁。 - 依赖版本兼容问题:部分旧版异步MySQL驱动(如aiomysql)在Python3.9环境下存在连接超时回收的引用处理bug,会导致绑定的上层任务被提前销毁。
修复方案
1. 修正MySQL资源释放逻辑
将所有mydb.close()修改为await mydb.close(),更推荐使用异步上下文管理器自动管理连接、游标资源,避免遗漏释放逻辑:
@commands.Cog.listener("on_message") async def on_message(self, message): if message.author.bot: return if message.type != discord.MessageType.default: return if isinstance(message.channel, discord.channel.DMChannel): return prefixes = ["!", "?", "=", ".", ",", "@", "#", "/", '§', "$", "%", "&", ":?", "*", "<", ">", "-", "bday"] for prefix in prefixes: if message.content.startswith(str(prefix)): return # 冷却校验逻辑后续替换,暂时保留原有逻辑也可先测试MySQL修复效果 global member_cooldown_list member_cooldown_list = [i for i in member_cooldown_list if i[1] + cooldown_val > int(time.time())] member_index = next((i for i, v in enumerate(member_cooldown_list) if v[0] == message.author.id), None) if member_index is not None: if member_cooldown_list[member_index][1] + cooldown_val > int(time.time()): return member_cooldown_list.append((message.author.id, int(time.time()))) count = 1 # 用异步上下文管理器管理连接和游标 async with await getConnection() as mydb: async with mydb.cursor() as mycursor: await mycursor.execute("SELECT ignore_role_id, bonus_role_id FROM guild_role_settings WHERE guild_id = %s", (message.author.guild.id,)) in_database = await mycursor.fetchone() if in_database: if in_database[0] is not None: role_list = in_database[0].split(" ") for roleid in role_list: role = message.author.guild.get_role(int(roleid)) if role in message.author.roles: return if in_database[1] is not None: role_list = in_database[1].split(" ") for roleid in role_list: role = message.author.guild.get_role(int(roleid)) if role in message.author.roles: count += 1 await mycursor.execute("SELECT ignore_channel_id FROM guild_channel_settings WHERE guild_id = %s", (message.author.guild.id,)) in_database1 = await mycursor.fetchone() if in_database1: if in_database1[0] is not None: channel_list = in_database1[0].split(" ") for channelid in channel_list: if int(message.channel.id) == int(channelid): return await mycursor.execute("SELECT * FROM guild_message_count WHERE guild_id = %s AND user_id = %s", (message.author.guild.id, message.author.id)) in_database2 = await mycursor.fetchone() if in_database2: await mycursor.execute("UPDATE guild_message_count SET user_id = %s, message_count = message_count + %s WHERE guild_id = %s AND user_id = %s", (message.author.id, count, message.author.guild.id, message.author.id)) else: await mycursor.execute("INSERT INTO guild_message_count (user_id, message_count, guild_id) VALUES (%s, %s, %s)", (message.author.id, count, message.author.guild.id)) await mydb.commit()
上下文管理器会自动处理游标、连接的关闭逻辑,无论代码正常执行还是中途return都不会遗漏资源释放。
2. 替换全局冷却实现
弃用自行维护的全局冷却列表,改用discord.py自带的CooldownMapping实现异步安全的用户冷却,避免竞态问题:
from discord.ext.commands import Cooldown, CooldownMapping # 在Cog的初始化方法中定义冷却规则 def __init__(self, bot): self.bot = bot # 规则:每个用户cooldown_val秒内最多触发1次on_message逻辑 self.msg_cooldown = CooldownMapping.from_cooldown(1, cooldown_val, lambda msg: msg.author.id) # 在on_message开头加入冷却校验,替换原有全局列表的冷却逻辑 @commands.Cog.listener("on_message") async def on_message(self, message): # ...原有消息过滤逻辑(机器人消息、DM消息、前缀消息等)不变 bucket = self.msg_cooldown.get_bucket(message) if bucket and bucket.update_rate_limit(): return # ...后续业务逻辑不变
3. 升级依赖版本
将你使用的异步MySQL驱动(如aiomysql)升级到0.1.1及以上稳定版,discord.py升级到2.0+稳定版,适配Python3.9的asyncio特性。
内容的提问来源于stack exchange,提问作者Razzer
相关产品推荐
相关产品推荐

