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

Python asyncio.Task异常未触发直至主任务完成的技术问询

Why Your Asyncio Program Doesn't Crash Immediately When a Task Throws an Exception

Let's break down why your code behaves this way, and point you to the asyncio code that handles these unawaited task exceptions.

The Core Issue: Unawaited Tasks Store Exceptions Instead of Crashing the Loop

When you create an asyncio.Task (like you did with asyncio.Task(coro1())) but never await it or retrieve its result, asyncio doesn't immediately propagate the exception to the event loop. Instead, it:

  • Stores the exception in the Task object's _exception attribute
  • Marks the Task as finished
  • Waits until either:
    1. You explicitly call task.result() or task.exception() (which will re-raise the exception)
    2. The Task is garbage collected (at which point asyncio logs a warning about the unhandled exception)
    3. The event loop exits entirely

In your code, coro2 is the main task that run_until_complete is waiting on. The asyncio.sleep(10) keeps this task alive for 10 seconds, so the event loop keeps running even though the coro1 tasks have already thrown exceptions. Only when coro2 finishes does the loop exit.

Where Asyncio Handles These Uncaught Task Exceptions

Let's look at the key parts of the asyncio source code that manage this behavior:

1. Task _step Method: Capturing the Exception

When a Task executes and hits an exception, the _step method (in asyncio.tasks.Task) catches it and calls _set_exception:

# Simplified snippet from asyncio.tasks
def _step(self, exc=None):
    try:
        if exc is None:
            result = self._coro.send(None)
        else:
            result = self._coro.throw(exc)
    except StopIteration as exc:
        self._set_result(exc.value)
    except Exception as exc:
        self._set_exception(exc)  # This stores the exception
    ...

2. _set_exception Stops the Task Without Propagating

The _set_exception method marks the Task as finished but doesn't trigger any loop termination:

def _set_exception(self, exception):
    if self._state != _PENDING:
        return
    self._exception = exception
    self._state = _FINISHED
    self._schedule_callbacks()  # Runs any attached callbacks, but not loop-wide error handling

3. Garbage Collection Logs Unhandled Exceptions

If the Task is never awaited or queried for its result, its __del__ method will eventually trigger a warning:

def __del__(self):
    if not self._done:
        ...
    elif self._exception is not None and not self._log_destroy_pending:
        self._log_destroy_pending = True
        logger.warning(
            "Task exception was never retrieved",
            exc_info=(type(self._exception), self._exception, self._exception.__traceback__)
        )

Why Your run_until_complete Breakpoint Didn't Trigger

The run_until_complete method only handles exceptions from the specific future it's waiting on (in your case, the coro2 task). The exceptions from the coro1 tasks are stored in their own Task objects, not propagated to the main future. That's why the except block in run_until_complete never fires.

How to Fix This (If You Want Immediate Crashes)

If you want unhandled task exceptions to crash the loop immediately, you have a few options:

  • Await all tasks you create (e.g., use asyncio.gather(*ts) instead of just creating them)
  • Attach a callback to each task that re-raises the exception into the loop:
    def handle_task_exception(task):
        try:
            task.result()
        except Exception as e:
            loop.call_exception_handler({"exception": e, "task": task})
    
    for task in ts:
        task.add_done_callback(handle_task_exception)
    
  • Use asyncio.create_task (Python 3.7+) and enable loop.set_exception_handler to catch unhandled exceptions.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:02:53