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

asyncio协程捕获异常后异常行为:多任务时循环未正常终止

我之前在写多协程下载脚本时也碰到过类似的“循环异常重启”问题,仔细看了你的代码和描述,大概率是这几个点出了问题,咱们一步步来捋:

1. 日志混淆导致的误解(最可能的原因)

你说单任务正常、多任务出问题,很大概率是不同任务的日志混在一起,让你误以为是同一个任务的循环重启。比如任务A的attemps耗尽抛出异常,此时任务B刚好在执行自己的attemps=2的循环,日志叠在一起就像任务A重启了。

解决办法很简单:给每个downloader任务加上唯一标识(比如你循环里的id),打印日志的时候带上这个id,就能清晰区分不同任务的执行流程:

async def downloader(session, url, spe_params, path, name, writer, task_id, max_features=5000, sleeping=5):
    try:
        pagging = True
        while pagging:
            attemps = 3
            while attemps:
                try:
                    async with session.get(url, params=p) as res:
                        if res.status != 200:
                            raise aiohttp.ClientError
                        res_json = await res.json()
                except (aiohttp.ClientError, asyncio.TimeoutError, json.JSONDecodeError) as e:
                    message = f'Task {task_id} Error fetching: {str(e)} | Remaining attempts: {attemps-1}'
                    print(message)
                    attemps -= 1
                    await asyncio.sleep(sleeping)
                # ... 其余逻辑
            if not attemps:
                raise DownloadError(f'Task {task_id} failed after 3 attempts')
    except DownloadError as e:
        print(e)
        return  # 明确终止协程

然后在main里传id:

for id in field_id:
    task = loop.create_task(downloader(session, ..., task_id=id))

2. 协程未明确终止

你的except DownloadError块是空的,虽然理论上捕获异常后协程会走到函数末尾自动终止,但在多协程调度的场景下,有时候会出现预期外的流程(比如asyncio的任务调度残留)。加上return语句明确终止协程,就能彻底避免这种情况:

except DownloadError as e:
    # 可以在这里记录错误日志
    print(f"Download failed for {name}: {e}")
    return  # 直接结束协程,不会继续执行任何循环

3. 共享可变参数导致的状态污染

如果你的spe_params或者分页参数p是可变对象(比如字典),多个协程同时修改它的话,会导致不同任务的分页逻辑混乱,看起来像是某个任务的循环“重启”。解决办法是给每个任务创建独立的参数副本:

# 在downloader函数内部,创建参数副本,避免多个协程共享修改
p = spe_params.copy()  # 如果是字典类型
# 或者根据需要深拷贝:import copy; p = copy.deepcopy(spe_params)

4. 内层循环break的逻辑漏洞

你的内层循环里,当res_json['totalFeatures'] == number_returned时,设置pagging=False然后break——这个break只跳出了内层的attemps循环,外层的pagging循环会在下一次迭代时检查pagging状态。如果number_returned没有被正确赋值(比如没把当前返回的特征数量赋值给它),会导致pagging一直为True,外层循环反复执行,看起来像是任务重启。

确保number_returned被正确设置:

else:
    number_returned = len(res_json['features'])  # 假设特征存在features字段里
    if res_json['totalFeatures'] == number_returned:
        pagging = False
        break
    # 其余分页逻辑,比如更新p里的分页参数

最后验证

按上面的修改后,先跑少量多任务测试,观察带task_id的日志,就能确认是不是日志混淆,或者协程是否真的在异常后终止。如果还有问题,再检查session的使用(确保session是在main里创建并共享,每个任务用同一个session是没问题的,但不要在任务里重复创建session)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:28:59