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

