
Python asyncio.TaskGroup은 여러 비동기 작업을 하나의 구조 안에서 만들고, 그 구조를 빠져나갈 때 모든 작업이 끝났는지 확인하는 API입니다. 단순히 여러 coroutine 결과를 모으는 도구라기보다, 관련된 작업 묶음을 하나의 실패 단위로 다루기 위한 기능에 가깝습니다.
실무 기준은 명확합니다. 여러 작업 중 하나가 실패하면 나머지도 더 진행할 필요가 없는 경우에는 TaskGroup이 잘 맞습니다. 반대로 각 작업이 서로 독립적이고 일부 실패 후에도 나머지 결과를 계속 받고 싶다면 asyncio.gather()의 동작을 먼저 확인하는 편이 낫습니다.

TaskGroup은 무엇을 보장하나
Python 공식 문서 기준으로 TaskGroup은 비동기 context manager입니다. async with 블록 안에서 create_task()로 작업을 추가하고, 블록을 빠져나갈 때 그룹 안의 모든 작업을 기다립니다. 이 흐름 덕분에 작업을 만들고 잊어버리는 실수를 줄일 수 있습니다.
import asyncio
async def fetch_user(user_id):
await asyncio.sleep(0.1)
return {"id": user_id}
async def main():
async with asyncio.TaskGroup() as tg:
user = tg.create_task(fetch_user(1))
profile = tg.create_task(fetch_user(2))
print(user.result(), profile.result())
asyncio.run(main())
위 코드에서 async with 블록이 끝난 뒤에는 user와 profile 작업이 모두 완료된 상태입니다. 결과가 필요하면 블록 밖에서 result()를 읽을 수 있습니다. 작업을 추가할 수 있는 범위와 결과를 읽는 시점이 코드 구조로 분리되는 점이 핵심입니다.
gather와 무엇이 다른가
asyncio.gather()도 여러 awaitable을 동시에 실행하고 결과 목록을 돌려줍니다. 기본값인 return_exceptions=False에서는 첫 예외가 호출자에게 전달됩니다. 다만 이때 다른 awaitable이 자동으로 모두 취소되는 방식은 아닙니다.
TaskGroup은 더 강한 구조적 동시성 기준을 제공합니다. 그룹 안의 작업 하나가 asyncio.CancelledError가 아닌 예외로 실패하면 남은 작업을 취소하고, 모두 정리된 뒤 예외를 묶어 전달합니다. 그래서 "이 작업들은 함께 성공하거나 함께 정리되어야 한다"는 관계를 코드에 드러내기 좋습니다.
| 구분 | asyncio.gather() |
asyncio.TaskGroup |
|---|---|---|
| 주된 목적 | 여러 결과를 순서대로 모음 | 관련 작업 묶음을 구조적으로 관리 |
| 첫 실패 후 동작 | 기본값에서는 첫 예외를 전달하고 다른 작업은 계속 실행될 수 있음 | 남은 작업을 취소하고 정리한 뒤 예외를 전달 |
| 결과 형태 | 입력 순서와 같은 결과 목록 | 각 task 객체의 result()를 직접 읽음 |
| 적합한 경우 | 작업들이 서로 독립적인 경우 | 작업들이 하나의 처리 단위인 경우 |

실패하면 어떤 일이 생기나
TaskGroup에서 첫 번째 비취소 예외가 발생하면 그룹은 더 이상 새 작업을 받지 않고, 남은 작업에 취소를 요청합니다. 이후 모든 작업이 끝날 때까지 기다린 다음, 실패 예외들을 ExceptionGroup 또는 BaseExceptionGroup으로 묶어 올립니다.
이 동작은 실패를 숨기기 위한 것이 아닙니다. 동시에 여러 작업이 실패할 수 있는 상황에서 관련 예외들을 하나의 예외 그룹으로 보존하기 위한 방식입니다. PEP 654는 여러 관련 없는 예외를 함께 전달하고 except*로 처리하기 위해 ExceptionGroup과 except* 문법을 도입했습니다.

try:
async with asyncio.TaskGroup() as tg:
tg.create_task(read_config())
tg.create_task(connect_backend())
tg.create_task(warm_cache())
except* OSError as errors:
for error in errors.exceptions:
print(type(error).__name__, error)
except*는 예외 그룹 안에서 특정 타입에 해당하는 예외를 처리할 때 사용합니다. 일반적인 except와 섞어서 같은 try 문에 쓰는 문법은 아닙니다. 예외 그룹을 발생시키는 API를 다룰 때만 필요한 문법으로 보는 것이 좋습니다.
취소 처리는 왜 조심해야 하나
TaskGroup은 내부적으로 취소를 사용해 실패한 그룹을 정리합니다. 공식 문서는 asyncio.CancelledError를 잡았다면 정리 작업 후 일반적으로 다시 전파해야 한다고 설명합니다. 이 예외를 삼켜 버리면 TaskGroup이나 asyncio.timeout()처럼 취소를 기반으로 동작하는 구조적 동시성 기능이 예상과 다르게 움직일 수 있습니다.
async def worker():
resource = await open_resource()
try:
await resource.run()
finally:
await resource.close()
대부분의 작업에서는 CancelledError를 직접 잡지 않고 finally에서 정리만 수행하는 편이 단순합니다. 취소를 반드시 잡아야 한다면 정리 후 다시 raise하는 것을 기본값으로 둡니다. 취소를 의도적으로 억제하는 코드는 드문 예외 상황으로 취급해야 합니다.
언제 TaskGroup을 선택할까
TaskGroup은 하나의 요청 처리, 하나의 배치 단계, 하나의 초기화 단계처럼 작업들이 같은 생명주기를 가질 때 잘 맞습니다. 예를 들어 설정 읽기, 외부 연결 확인, 캐시 준비가 모두 끝나야 다음 단계로 갈 수 있다면 한 그룹으로 묶는 편이 자연스럽습니다.
반대로 검색 결과 여러 건을 병렬로 가져오되 일부 실패를 결과 목록 안에서 처리하려는 경우에는 gather(return_exceptions=True) 같은 방식이 더 직접적일 수 있습니다. 이 경우에는 실패를 값처럼 다룰지, 전체 처리를 실패로 볼지부터 정해야 합니다.
| 상황 | 권장 선택 | 이유 |
|---|---|---|
| 하나의 단계 안에서 여러 필수 작업 실행 | TaskGroup |
하나 실패 시 남은 작업을 정리하는 기준이 명확함 |
| 독립 요청의 결과 목록 수집 | gather() |
입력 순서대로 결과를 받기 쉬움 |
| 일부 실패도 결과로 보존 | gather(return_exceptions=True) |
예외를 결과 목록 안에서 직접 판정할 수 있음 |
| 중첩된 하위 작업까지 실패 단위로 묶기 | TaskGroup |
구조적 동시성 의도가 코드에 드러남 |
버전 기준
TaskGroup은 Python 3.11에 추가되었습니다. Python 3.13에서는 비활성 그룹에 coroutine을 추가하려 할 때 해당 coroutine을 닫는 동작이 문서화되었고, 동시 내부·외부 취소 처리와 취소 카운트 보존이 개선되었습니다. Python 3.14 문서 기준으로 TaskGroup.create_task()는 전달받은 keyword argument를 loop.create_task()로 넘깁니다.
라이브러리나 서비스 코드에서 사용할 때는 실행 환경의 Python 버전을 먼저 확인해야 합니다. Python 3.10 이하를 지원해야 한다면 표준 라이브러리의 TaskGroup을 바로 사용할 수 없습니다.
FAQ
TaskGroup을 쓰면 모든 예외가 하나만 나오나?
그렇지 않습니다. 여러 작업이 실패할 수 있으므로 ExceptionGroup이나 BaseExceptionGroup으로 묶여 전달될 수 있습니다. 특정 타입만 처리하려면 except*를 봐야 합니다.
CancelledError를 except Exception으로 잡을 수 있나?
일반적으로 잡히지 않습니다. asyncio.CancelledError는 BaseException을 직접 상속합니다. 취소는 보통 정상적인 정리 신호로 보고, 필요한 정리 후 다시 전파하는 방식이 안전합니다.
TaskGroup이 gather를 완전히 대체하나?
완전한 대체 관계는 아닙니다. TaskGroup은 관련 작업의 생명주기와 실패 정리를 구조화할 때 좋고, gather()는 여러 결과를 목록으로 모을 때 단순합니다. 실패를 어떤 단위로 볼지가 선택 기준입니다.
'프로그래밍 > C, C++, Java, Python' 카테고리의 다른 글
| Java ScopedValue 기준 (0) | 2026.09.22 |
|---|---|
| Python TypeIs 기준 (1) | 2026.09.19 |
| Python eager task factory 기준 (0) | 2026.09.18 |
| Python asyncio.Runner 기준 (0) | 2026.09.16 |
| Python Thread context 기준 (0) | 2026.09.15 |





