본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 비동기 처리의 핵심 : asyncio.gather와 asyncio.wait 에러 핸들링 차이 분석 및 3가지 해결 방법

by Papa Martino V 2026. 2. 25.
728x90

asyncio.gather와 asyncio.wait
asyncio.gather와 asyncio.wait

 

파이썬의 asyncio 라이브러리를 활용하여 고성능 비동기 애플리케이션을 개발할 때, 개발자들이 가장 빈번하게 마주치는 고민 중 하나는 "여러 개의 태스크를 어떻게 효율적으로 동시에 실행하고 제어할 것인가?"입니다. 특히 실행 중 발생할 수 있는 예외(Exception)를 어떻게 처리하느냐에 따라 프로그램의 안정성이 결정됩니다. 본 포스팅에서는 실무 환경에서 가장 많이 쓰이는 두 함수, asyncio.gatherasyncio.wait의 기술적 메커니즘을 심층 분석하고, 에러 핸들링 시 발생하는 결정적인 차이점과 상황별 최적의 해결 전략을 제시합니다.


1. asyncio.gather vs asyncio.wait: 동작 원리의 이해

두 함수 모두 여러 코루틴을 동시에 실행하는 목적은 같지만, 반환 값의 형태와 예외가 발생했을 때의 전파 방식에서 큰 차이를 보입니다.

asyncio.gather의 특징

gather는 결과 중심적입니다. 전달된 모든 코루틴이 완료되면 그 결과값들을 리스트 형태로 순서대로 반환합니다. 만약 실행 도중 예외가 발생하면 기본적으로 호출자에게 즉시 예외를 전파하며, 다른 태스크들은 배경에서 계속 실행되지만 제어가 까다로워질 수 있습니다.

asyncio.wait의 특징

wait는 상태 중심적입니다. 태스크의 완료 여부(Done, Pending)를 집합(Set) 형태로 반환하며, 예외가 발생하더라도 이를 즉시 던지지 않고 완료된 태스크 객체 안에 보관합니다. 개발자는 return_when 옵션을 통해 제어권을 언제 되찾아올지 정교하게 설계할 수 있습니다.


2. 에러 핸들링 방식의 결정적 차이 요약

두 방식의 차이를 한눈에 파악할 수 있도록 표로 정리하였습니다.

비교 항목 asyncio.gather asyncio.wait
주요 반환값 태스크 결과값의 리스트 (List) (Done set, Pending set) 튜플
기본 예외 처리 첫 번째 에러 발생 시 즉시 예외 발생 에러가 발생해도 직접 던지지 않음
return_exceptions 옵션 지원함 (True 설정 시 에러를 결과로 취급) 지원하지 않음 (태스크 객체에서 확인 필요)
태스크 순서 보장 입력 순서대로 결과 리스트 구성 순서 보장 없음 (Set 자료형 사용)
사용 권장 상황 모든 결과값이 한꺼번에 필요한 경우 태스크 진행 상태를 세밀하게 제어할 경우

3. Sample Example: 에러 발생 시의 코드 흐름 비교

실제 코드를 통해 에러가 어떻게 전파되는지 살펴보겠습니다. 두 코루틴 중 하나에서 의도적으로 ValueError를 발생시키는 상황입니다.

예제 1: asyncio.gather를 이용한 에러 처리


import asyncio

async def task_success():
    await asyncio.sleep(1)
    return "성공"

async def task_fail():
    await asyncio.sleep(0.5)
    raise ValueError("작업 중 에러 발생!")

async def run_gather():
    # return_exceptions=True로 설정하면 에러 객체가 리스트에 포함됩니다.
    results = await asyncio.gather(task_success(), task_fail(), return_exceptions=True)
    print(f"Gather 결과: {results}")

asyncio.run(run_gather())

예제 2: asyncio.wait를 이용한 에러 처리


import asyncio

async def run_wait():
    t1 = asyncio.create_task(task_success())
    t2 = asyncio.create_task(task_fail())
    
    done, pending = await asyncio.wait([t1, t2], return_when=asyncio.FIRST_EXCEPTION)
    
    for task in done:
        try:
            result = task.result()
            print(f"작업 완료 결과: {result}")
        except Exception as e:
            print(f"에러 확인: {e}")

asyncio.run(run_wait())

4. 전문가가 제안하는 3가지 실전 해결 전략

대규모 트래픽을 처리하는 백엔드 시스템에서는 다음과 같은 전략을 권장합니다.

  1. 결과 데이터의 무결성이 중요한 경우 (gather): return_exceptions=True를 사용하여 전체 결과를 수집한 뒤, 리스트 내부에 Exception 인스턴스가 있는지 isinstance() 함수로 필터링하여 처리하세요.
  2. 타임아웃 및 조기 중단이 필요한 경우 (wait): FIRST_EXCEPTION 옵션을 사용하여 하나라도 실패하면 즉시 로깅하고 나머지 작업을 취소(cancel)하는 로직을 구성하여 리소스를 방어하세요.
  3. 독립적인 작업 수행: 각 코루틴 내부에서 try-except 문으로 감싸 에러를 자체적으로 소화하게 만들면, 상위 호출부의 로직이 단순해집니다.

5. 결론 및 요약

비동기 프로그래밍에서 에러 핸들링은 단순히 try-except를 쓰는 것 이상의 설계적 고민이 필요합니다. 결과값의 순서와 집합이 중요하다면 gather를, 실행 상태에 따른 동적인 제어가 우선이라면 wait를 선택하는 것이 가장 효과적인 해결 방법입니다.

 

참조 및 출처:
- Python Software Foundation 공식 문서 (docs.python.org/3/library/asyncio-task.html)
- Real Python: Async IO in Python: A Complete Walkthrough
- Stack Overflow Python Asyncio Performance Discussion

728x90