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

Python asyncio.Future 无显式操作时自行取消抛出CancelledError问题

问题根本原因

这个异常是PENDING状态的asyncio.Future被垃圾回收时自动触发取消导致的,你没有手动调用cancel、事件循环也未关闭却触发异常,完全是代码逻辑bug导致Future失去有效强引用、永远无法被设置结果:

  • 第一处核心错误:你在hold方法入口就执行pop操作,会把已存在的待处理请求从__held_requests中提前移除。正常情况下你的业务逻辑会遍历__held_requests找到待处理的请求执行、再给对应Future设置结果,提前pop会导致处理逻辑找不到这个请求,对应的Future永远不会被执行set_result()/set_exception()。
  • 第二处核心错误:你混用了存储结构API:从pop(key, default)的写法可以判断__held_requests应该是字典(Python原生set的pop方法不接收参数,传参会直接抛TypeError),但你存入新请求时用了set专属的add()方法,这会导致新创建的RatingRequestPerishable和Future根本没有被正确存入存储结构。就算你用了自定义支持按key弹出的set,也存在竞态漏洞:并发场景下多个协程会同时检测到key不存在,各自创建新Future,只有最后一个创建的实例会被存入集合,之前创建的Future不会被处理逻辑感知。

Python会在运行时自动分析局部变量生命周期,一个变量如果后续不再被访问,会被提前释放引用。当PENDING状态的Future没有任何强引用、被GC回收时,asyncio会自动调用它的cancel()方法,抛出你看到的asyncio.exceptions.CancelledError。
你观察到的“每隔一次运行触发异常”的现象和这个bug完全匹配:第一次调用创建的Future被正确持有,可以正常等待结果返回;第二次调用要么提前把已存的Future弹出导致引用丢失,要么创建的新Future没被正确存入存储结构,最终被GC自动取消。

修复方案
  • 统一__held_requests为普通字典类型,key为request_key,value为对应的RatingRequestPerishable实例,存入新请求时用字典赋值语法:self.__held_requests[request_key] = rating_perishable,不要用add()方法。
  • 移除方法入口的pop逻辑,改为先查询key是否存在:如果存在直接取对应Future等待;不存在再创建新实例存入字典。
  • 只有当请求处理完成、已经给对应Future设置完结果/异常后,再把key从字典中删除,禁止提前移除待处理项。
  • 高并发场景下,给字典的检查、创建、写入操作加异步锁,避免竞态条件导致重复创建Future。

修复后的核心代码参考:

async def hold(self, request_key):
    logger.info(f"HOLDING: request_key={request_key}")
    # 仅查询,不提前弹出元素
    ongoing_rating_perishable = self.__held_requests.get(request_key, None)
    if ongoing_rating_perishable is None:
        future = asyncio.get_running_loop().create_future()
        rating_perishable = RatingRequestPerishable(request_key, future, holding=True)
        # 字典用赋值语法存储
        self.__held_requests[request_key] = rating_perishable
    else:
        future = ongoing_rating_perishable.rating_future

    try:
        print(f"state: {future._state}")
        result = await future
    except asyncio.CancelledError as err:
        print(traceback.format_exc())
        # 异常时清理无效缓存
        if request_key in self.__held_requests and self.__held_requests[request_key].rating_future is future:
            del self.__held_requests[request_key]
        raise

    return result

# 业务逻辑处理完成后调用该方法设置结果、清理缓存
def release(self, request_key, result):
    perishable = self.__held_requests.pop(request_key, None)
    if perishable and not perishable.rating_future.done():
        perishable.rating_future.set_result(result)

内容的提问来源于stack exchange,提问作者LetsDoThis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 02:42:10