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

Python异常未按预期处理:WebSocket超时错误未写入日志

异步异常未被预期捕获的原因分析

问题描述

用户编写了如下异步异常处理代码:

try:
    jobs1 = await data.get_jobs()
    jobs2 = await user.get_jobs()
    tasks = [asyncio.create_task(x) for x in [loop()] + jobs1 + jobs2]
    print(f"{len(tasks)} tasks")
    await asyncio.gather(*tasks)
except Exception as e:
    e = str(e)
    [x.cancel() for x in tasks]
    if not any([x in e for x in IGNORE_ERRORS]):
        user.log({"error": f"{e} \n {traceback.format_exc()}"})

预期所有异常都通过user.log写入log.json,但有一个异常直接打印到控制台:

data transfer failed
Traceback (most recent call last):
  File "D:\software\miniconda\lib\site-packages\websockets\legacy\protocol.py", line 959, in transfer_data
    message = await self.read_message()
  File "D:\software\miniconda\lib\site-packages\websockets\legacy\protocol.py", line 1029, in read_message
    frame = await self.read_data_frame(max_size=self.max_size)
  File "D:\software\miniconda\lib\site-packages\websockets\legacy\protocol.py", line 1104, in read_data_frame
    frame = await self.read_frame(max_size)
  File "D:\software\miniconda\lib\site-packages\websockets\legacy\protocol.py", line 1161, in read_frame
    frame = await Frame.read(
  File "D:\software\miniconda\lib\site-packages\websockets\legacy\framing.py", line 68, in read
    data = await reader(2)
  File "D:\software\miniconda\lib\asyncio\streams.py", line 707, in readexactly
    await self._wait_for_data('readexactly')
  File "D:\software\miniconda\lib\asyncio\streams.py", line 501, in _wait_for_data
    await self._waiter
    length = fut.result()
  File "D:\software\miniconda\lib\asyncio\windows_events.py", line 826, in _poll
    value = callback(transferred, key, ov)
  File "D:\software\miniconda\lib\asyncio\windows_events.py", line 494, in finish_recv
    return ov.getresult()
OSError: [WinError 121] 信号灯超时时间已到

原因分析

  1. 异常触发于任务取消后的后续操作
    当asyncio.gather因某任务抛出异常进入except块后,你调用[x.cancel() for x in tasks]取消所有任务。但websockets的连接协程(如transfer_data)在被取消时,内部的IO操作会抛出OSError: [WinError 121]。这个异常是在任务被取消后触发的,此时asyncio.gather的异常处理流程已经结束,无法捕获后续抛出的异常,导致asyncio将其直接打印到控制台。

  2. 未处理被取消任务的残留异常
    asyncio.create_task创建的任务被取消后,如果抛出额外异常且未被显式await或处理,asyncio会将这类未捕获的任务异常默认输出到控制台。你的代码在取消任务后,没有处理这些任务可能抛出的后续异常。

  3. 代码执行顺序的逻辑漏洞
    except块中先将异常转为字符串,再执行任务取消操作。此时取消任务触发的异常无法被当前except块捕获——当前块只负责处理await asyncio.gather(*tasks)抛出的初始异常,任务取消后的异常属于独立执行流,不在当前捕获范围内。

解决建议

方案1:处理取消任务后的异常

在取消任务后,显式await每个任务并处理可能的异常,避免未捕获的异常被打印:

import traceback
import asyncio

try:
    jobs1 = await data.get_jobs()
    jobs2 = await user.get_jobs()
    tasks = [asyncio.create_task(x) for x in [loop()] + jobs1 + jobs2]
    print(f"{len(tasks)} tasks")
    await asyncio.gather(*tasks)
except Exception as e:
    e_str = str(e)
    # 取消所有任务
    for task in tasks:
        task.cancel()
    # 处理取消任务后可能抛出的异常
    for task in tasks:
        try:
            await task
        except (asyncio.CancelledError, Exception) as te:
            te_str = str(te)
            if not any(ignore in te_str for ignore in IGNORE_ERRORS):
                user.log({"error": f"Task cancelled error: {te_str} \n {traceback.format_exc()}"})
    # 记录初始异常
    if not any(ignore in e_str for ignore in IGNORE_ERRORS):
        user.log({"error": f"{e_str} \n {traceback.format_exc()}"})

方案2:使用return_exceptions=True统一捕获异常

给asyncio.gather添加return_exceptions=True参数,这样所有任务的异常会被收集为返回值,而非直接抛出,可统一处理所有异常:

import traceback
import asyncio

jobs1 = await data.get_jobs()
jobs2 = await user.get_jobs()
tasks = [asyncio.create_task(x) for x in [loop()] + jobs1 + jobs2]
print(f"{len(tasks)} tasks")
results = await asyncio.gather(*tasks, return_exceptions=True)

# 遍历结果处理所有异常
for result in results:
    if isinstance(result, Exception):
        e_str = str(result)
        if not any(ignore in e_str for ignore in IGNORE_ERRORS):
            user.log({"error": f"{e_str} \n {traceback.format_exc()}"})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 11:24:56