프로그래밍/C, C++, Java, Python

Python asyncio timeout 기준 정리

포도알77 2026. 7. 1. 11:42

본문 중간의 쿠팡 추천 상품 구매시 쿠팡 파트너스에서 일정액의 수수료를 제공받습니다.

Python asyncio timeout 기준 정리

asyncio에서 시간을 제한하고 싶을 때는 비슷해 보이는 API가 여럿 있다. 2026년 7월 1일 기준 Python 3.14.6 공식 문서를 보면 asyncio.timeout()timeout_at()은 비동기 컨텍스트 매니저이고, wait_for()는 단일 awaitable을 감싸는 함수이며, wait()는 timeout이 나도 TimeoutError를 던지지 않는다.

실무에서는 "하나의 await만 제한할지", "여러 await가 들어 있는 논리 블록 전체를 제한할지", "상대 시간인지 절대 deadline인지"를 먼저 나누면 선택이 빨라진다. 이 글은 asyncio.timeout(), timeout_at(), wait_for()의 차이와 무난한 선택 기준만 정리한다.

먼저 무엇이 다를까?

API 형태 무난한 사용 상황
asyncio.timeout(seconds) 비동기 컨텍스트 매니저 여러 await가 들어 있는 코드 블록 전체에 상대 시간 제한을 걸고 싶을 때
asyncio.timeout_at(when) 비동기 컨텍스트 매니저 이벤트 루프 시계 기준의 절대 deadline을 공유해야 할 때
asyncio.wait_for(aw, timeout) 함수 단일 awaitable 하나에 timeout을 적용하고 싶을 때
asyncio.wait(aws, timeout=...) 함수 timeout 시 예외 대신 완료된 작업과 남은 작업을 나눠 받고 싶을 때

공식 문서 기준 asyncio.timeout()timeout_at()은 3.11에서 추가됐다. wait_for()는 더 오래된 API지만, timeout이 발생하면 대상 task를 취소하고 실제 취소가 끝날 때까지 기다릴 수 있어서 총 대기 시간이 지정한 timeout보다 길어질 수 있다.

asyncio.timeout()은 언제 고르면 될까?

asyncio.timeout()async with 블록 안의 작업 전체를 하나의 시간 제한으로 묶고 싶을 때 가장 읽기 쉽다. 예를 들어 연결 생성, 초기 핸드셰이크, 첫 응답 대기를 한 덩어리로 보고 2초 안에 끝나야 한다면 개별 await마다 wait_for()를 겹치기보다 블록 바깥에서 timeout을 거는 편이 의도가 분명하다.

공식 문서는 이 컨텍스트 매니저가 deadline을 다시 잡을 수 있고, 현재 deadline 확인과 만료 여부 확인도 가능하다고 설명한다. 시작 시점에는 시간을 모른다가 중간에 확정되는 흐름이라면 asyncio.timeout(None)으로 열고 나중에 reschedule()하는 패턴도 가능하다.

import asyncio

async def fetch_with_budget():
    async with asyncio.timeout(2.0):
        await open_connection()
        await send_request()
        return await read_response()

timeout_at()은 왜 따로 있을까?

timeout_at()은 상대 시간 대신 절대 deadline을 받는다. 그래서 상위 호출자가 이미 loop.time() 기준 마감 시각을 계산해 두었고, 하위 함수 여러 개가 그 예산을 공유해야 할 때 더 적합하다.

예를 들어 요청 하나에 5초 예산이 있고, 그 안에서 DNS 조회, 외부 API 호출, 후처리를 나눠 써야 한다면 각 함수에 "남은 시간"을 다시 계산해서 넘기기보다 같은 deadline을 넘기는 편이 drift를 줄이기 쉽다. 공식 문서도 timeout_at()을 절대 시각 버전으로 설명한다.

 

 

wait_for()는 무엇이 다를까?

wait_for()는 awaitable 하나를 감싸는 형태라서 짧고 직접적이다. "이 호출 하나만 500ms 안에 끝나야 한다"처럼 단일 작업 제한에는 여전히 잘 맞는다.

다만 timeout 시 동작은 생각보다 강하다. 공식 문서에 따르면 timeout이 발생하면 대상 awaitable을 취소하고 TimeoutError를 올린다. 또 취소가 실제로 완료될 때까지 기다리기 때문에 총 대기 시간이 지정한 timeout보다 늘어날 수 있다. 그래서 timeout을 "단순한 시계 알람"으로 기대하면 오해가 생길 수 있다.

import asyncio

async def read_once():
    return await asyncio.wait_for(read_message(), timeout=0.5)

어떤 기준으로 고르면 될까?

상황 무난한 선택 이유
여러 await를 하나의 예산으로 묶고 싶다 asyncio.timeout() 블록 단위 의도가 분명하고 중첩도 안전하게 다룰 수 있음
절대 deadline을 여러 함수에 공유해야 한다 asyncio.timeout_at() 이벤트 루프 시계 기준 deadline을 그대로 전달할 수 있음
개별 awaitable 하나만 제한하면 된다 asyncio.wait_for() 표현이 짧고 대상이 명확함
예외 없이 done/pending을 나눠 받고 싶다 asyncio.wait() timeout 시 남은 작업을 직접 정리하는 흐름에 맞음

예외 처리에서 자주 놓치는 점

asyncio.timeout()TaskGroup 같은 구조화된 동시성 도구는 내부적으로 cancellation을 사용한다. Python 공식 문서는 coroutine이 CancelledError를 잡고 삼켜 버리면 이런 구성 요소가 오동작할 수 있다고 설명한다. 정리 작업이 필요해 CancelledError를 잡더라도, 보통은 정리를 마친 뒤 다시 전파하는 편이 맞다.

또 하나는 asyncio.wait()wait_for()를 같은 부류로 보면 안 된다는 점이다. 공식 문서 기준 wait()는 timeout이 나도 TimeoutError를 던지지 않고, 끝나지 않은 task를 pending 집합으로 돌려준다. 반대로 wait_for()는 timeout을 예외로 보고 대상 awaitable 취소까지 시도한다.

운영에서 무난한 기본값

새 코드를 작성한다면 여러 단계의 작업 예산에는 asyncio.timeout(), 단일 awaitable에는 wait_for()를 우선 기준으로 두는 편이 읽기 쉽다. 절대 deadline을 이미 계산해 두는 구조라면 timeout_at()으로 통일하면 하위 함수에서 남은 시간 계산을 반복하지 않아도 된다.

Python 3.11 미만도 함께 지원해야 한다면 asyncio.timeout() 계열은 쓸 수 없다. 이 경우 호환성 때문에 wait_for()를 더 많이 쓰게 되지만, timeout 시 취소 전파와 총 대기 시간 증가 가능성은 코드 리뷰 기준에 분명히 남겨 두는 편이 안전하다.

자주 헷갈리는 질문

Q. asyncio.timeout() 안에서 timeout이 나면 블록 안의 작업은 취소될까?

그렇다. 이 기능은 cancellation을 내부적으로 사용해 overdue coroutine을 멈추는 방식이다. 다만 호출부에서는 컨텍스트 매니저 바깥에서 TimeoutError로 다루게 된다.

Q. wait_for()는 timeout이 나도 대상 작업이 계속 돌 수 있을까?

기본 동작은 대상 awaitable을 취소하는 쪽이다. 다만 공식 문서는 취소가 실제로 완료될 때까지 기다릴 수 있으므로, 호출 시간이 timeout 값과 정확히 일치한다고 기대하면 안 된다고 설명한다.

Q. 여러 task를 동시에 기다리면 항상 gather()와 timeout을 같이 쓰면 될까?

항상 그렇지는 않다. timeout이 블록 전체 예산인지, 개별 작업 제한인지 먼저 나눠야 한다. 그룹 전체 예산이면 asyncio.timeout()으로 감싸는 쪽이 더 읽기 쉽고, 일부 완료된 작업을 따로 회수해야 하면 asyncio.wait()가 더 맞을 수 있다.

정리

asyncio.timeout()은 블록 단위 시간 예산, timeout_at()은 절대 deadline 공유, wait_for()는 단일 awaitable 제한에 가장 잘 맞는다. 세 API 모두 "시간이 지나면 그냥 실패를 알린다" 수준이 아니라 cancellation과 예외 전파 방식이 다르기 때문에, timeout을 어디에 걸고 싶은지부터 분리해서 고르는 편이 안전하다.

2026년 7월 1일 기준 Python 3.14.6 공식 문서만 놓고 보면, 새 코드의 기본 출발점은 대체로 asyncio.timeout()이다. 다만 단일 호출 제한이나 3.10 이하 호환성이 중요하면 wait_for()가 더 현실적인 선택이 될 수 있다.

참고 자료

반응형
페이스북으로 공유카카오톡으로 공유카카오스토리로 공유트위터로 공유URL 복사