You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 10:39:02