본문 바로가기
Q&A2026년 4월 16일1분 읽기

Python asyncio.gather에서 일부 Task만 취소하고 나머지는 계속 실행하고 싶어요

데
데이터사이언스현
조회 470댓글 3

여러 외부 API를 병렬로 호출하는데, 일부는 느려서 타임아웃을 주고 다른 것들은 끝까지 기다리고 싶습니다.

results = await asyncio.gather(
    fetch_fast_api(),
    fetch_slow_api(),
    fetch_medium_api(),
)

한 호출이 예외 던지면 gather 전체가 같이 죽는 것 같은데, return_exceptions=True만 켜면 되는 건지, 아니면 asyncio.wait_for를 각각 걸어야 하는 건지 애매합니다. Python 3.11입니다.

댓글 3

김김영삼6개월 전

요구사항이 "일부만 타임아웃, 나머지는 기다린다"이면 각 태스크에 개별 타임아웃을 걸고 전체는 gather(..., return_exceptions=True)로 감싸는 게 정답입니다.


권장 패턴 (3.11+)
import asyncio

async def with_timeout(coro, timeout, name):
try:
async with asyncio.timeout(timeout):
return await coro
except TimeoutError:
return {"error": "timeout", "name": name}

async def main():
results = await asyncio.gather(
with_timeout(fetch_fast_api(), timeout=2, name="fast"),
with_timeout(fetch_slow_api(), timeout=10, name="slow"),
with_timeout(fetch_medium_api(), timeout=5, name="medium"),
return_exceptions=True,
)

포인트:



  • asyncio.timeout()은 3.11에서 추가된 컨텍스트 매니저로 wait_for보다 취소 전파가 깔끔합니다.

  • return_exceptions=True가 없으면 하나 실패 시 나머지 pending task가 자동 취소되지 않고 경고만 뜨는 "fire-and-forget" 상태가 돼서 위험합니다. 켜주세요.


쓰면 안 되는 패턴
# ❌ 이건 전체 호출 합쳐서 5초가 아니라
# "첫 호출만 5초 제한"이라 기대와 다르게 동작합니다.
await asyncio.wait_for(asyncio.gather(...), timeout=5)

위는 하나라도 5초 넘으면 전체가 cancel되어 "일부만 취소"와 정반대입니다.


TaskGroup 고려

"하나 실패 시 나머지 즉시 취소" 의미론이 필요하면 3.11 asyncio.TaskGroup이 더 명확합니다. 지금 요구사항은 그 반대라 gather + 개별 timeout 조합이 맞습니다.

코코딩하는민수6개월 전

저도 얼마 전에 wait_for(gather(...)) 실수로 반나절 날렸습니다. 의미상 "전체 타임아웃"처럼 보이는데 내부적으로는 각 태스크에 같은 데드라인을 전파하는 게 아니라 바깥만 감싸는 거라 헷갈려요. 개별 타임아웃 조합이 가장 안전합니다.

데데이터사이언스현6개월 전

asyncio.timeout() 컨텍스트 매니저로 재작성하니 각 API별 SLA가 코드에서 바로 읽히고 테스트도 훨씬 쉬워졌어요. return_exceptions 파트는 경고만 뜨고 끝나는 거라 무서운 함정이네요. 감사합니다!

Ctrl+Enter로 등록