프로그래밍/Python
[python] asyncio.gather() 함수와 asyncio.wait() 함수의 차이는?
채윤아빠
2025. 6. 26. 09:27
asyncio.gather()
와 asyncio.wait()
는 모두 여러 코루틴(또는 태스크)을 동시에 실행하고 그 결과를 기다리는 데 사용되는 asyncio
라이브러리의 핵심 함수입니다. 하지만 동작 방식과 사용 목적에 있어서 중요한 차이점이 있습니다.
asyncio.gather(*aws, return_exceptions=False)
- 목적: 여러 개의
awaitable
객체(코루틴, 태스크, 퓨처 등)를 그룹으로 묶어 함께 실행하고, 모든 awaitable이 완료될 때까지 기다린 후 그 결과를 수집합니다. - 반환 값: 모든
awaitable
의 결과가 담긴 리스트를 반환합니다. 이 리스트의 순서는 입력된awaitable
의 순서와 동일합니다. - 예외 처리
- 기본적으로
return_exceptions=False
입니다. 이 경우, 하나의awaitable
이라도 예외를 발생시키면gather()
는 즉시 나머지 실행 중인awaitable
을 취소하고 해당 예외를 발생시킵니다. return_exceptions=True
로 설정하면, 예외가 발생하더라도gather()
는 다른awaitable
의 실행을 계속 진행하고, 예외 객체를 결과 리스트에 포함시킵니다.
- 기본적으로
- 사용 시점
- 모든 태스크가 성공적으로 완료되어야 다음 단계로 진행할 수 있을 때.
- 모든 태스크의 결과를 한 번에 얻고 싶을 때.
- 데이터베이스 쿼리, 여러 API 호출, 파일 다운로드 등 독립적인 여러 작업을 동시에 수행하고 모든 결과를 취합해야 할 때 유용합니다.
예시
import asyncio
async def func1():
await asyncio.sleep(1)
print("func1 완료")
return "결과1"
async def func2():
await asyncio.sleep(2)
print("func2 완료")
return "결과2"
async def main_gather():
print("gather 시작")
results = await asyncio.gather(
func1(),
func2()
)
print(f"gather 결과: {results}")
if __name__ == "__main__":
asyncio.run(main_gather())
"""실행결과 >>>
gather 시작
func1 완료
func2 완료
gather 결과: ['결과1', '결과2']
"""
asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED)
- 목적: 여러 개의
awaitable
객체(주로 태스크)를 실행하고, 특정 조건(return_when
)이 충족될 때까지 기다린 후 완료된 태스크와 아직 실행 중인 태스크를 분리하여 반환합니다. - 반환 값:
(done, pending)
튜플을 반환합니다.done
: 조건이 충족되어 완료된 태스크들의 집합 (set)pending
: 아직 완료되지 않고 실행 중인 태스크들의 집합 (set)
return_when
인자asyncio.ALL_COMPLETED
(기본값): 모든 태스크가 완료될 때까지 기다립니다.pending
집합은 비어있게 됩니다. (이 경우gather
와 유사한 동작을 보일 수 있지만, 결과 반환 방식과 예외 처리 방식은 다릅니다.)asyncio.FIRST_COMPLETED
: 태스크 중 하나라도 완료되면 즉시 반환합니다.asyncio.FIRST_EXCEPTION
: 태스크 중 하나라도 예외를 발생시키면 즉시 반환합니다. 예외가 발생하지 않으면ALL_COMPLETED
처럼 모든 태스크가 완료될 때까지 기다립니다.
- 예외 처리:
wait()
자체는 태스크에서 발생한 예외를 직접적으로 발생시키지 않습니다. 예외가 발생한 태스크도done
집합에 포함되며, 해당 태스크의result()
메서드를 호출하거나await
할 때 예외가 다시 발생합니다. (이것이 위에 제시된asyncio.CancelledError
처리 예제에서await task
를 하는 이유입니다.) timeout
인자: 지정된 시간(초) 내에return_when
조건이 충족되지 않으면 함수가 반환됩니다. 이 경우 완료되지 않은 태스크는pending
집합에 포함됩니다.- 사용 시점
- 가장 빠른 태스크의 결과를 얻고 싶을 때 (예: 여러 웹 서비스 중 가장 먼저 응답하는 곳의 데이터를 사용).
- 특정 시간 제한 내에 작업이 완료되기를 기다리고, 그 이후에는 나머지 작업을 취소하거나 다른 처리를 하고 싶을 때.
- 태스크의 완료 여부를 세밀하게 제어해야 할 때.
예시
import asyncio
async def func3():
await asyncio.sleep(3)
print("func3 완료")
return "결과3"
async def func4():
await asyncio.sleep(1)
print("func4 완료")
return "결과4"
async def main_wait():
print("wait 시작 (FIRST_COMPLETED)")
task3 = asyncio.create_task(func3())
task4 = asyncio.create_task(func4())
done, pending = await asyncio.wait(
[task3, task4],
return_when=asyncio.FIRST_COMPLETED
)
print(f"wait 결과: done={done}, pending={pending}")
# 완료되지 않은 태스크 취소 (예시에서처럼 직접 처리해야 함)
for task in pending:
task.cancel()
try:
await task
except asyncio.CancelledError:
print(f"{task.get_name()} 취소됨")
if __name__ == "__main__":
asyncio.run(main_gather())
"""실행결과 >>>
wait 시작 (FIRST_COMPLETED)
func4 완료
wait 결과: done={<Task finished name='Task-3' coro=<func4() done, defined at D:\MyProj\python-test\general\asyncio\wait-example:8> result='결과4'>}, pending={<Task pending name='Task-2' coro=<func3() running at D:\MyProj\python-test\general\asyncio\wait-example:4> wait_for=<Future pending cb=[Task.task_wakeup()]>>}
Task-2 취소됨
"""
주요 차이점 요약
특징 | asyncio.gather() |
asyncio.wait() |
---|---|---|
목적 | 모든 awaitable 이 완료될 때까지 기다리고 결과 수집 |
특정 조건(return_when )에 따라 반환하며, 완료/대기 태스크 분리 |
반환 값 | 결과 리스트 | (done, pending) 튜플 (태스크 집합) |
예외 처리 | 기본적으로 첫 예외 발생 시 모든 태스크 취소 및 예외 발생 | 예외를 직접 발생시키지 않음; done 태스크의 result() 호출 시 예외 발생 |
유연성 | 단순 그룹 실행 및 결과 취합에 적합 | 완료 조건(return_when , timeout )을 세밀하게 제어 가능 |
인자 | *aws (awaitables), return_exceptions |
aws (awaitables), timeout , return_when |
주요 사용처 | 모든 서브 작업이 완료되어야 할 때 | 가장 빠른 작업의 결과가 필요하거나, 타임아웃 처리가 필요할 때 |
맺는말
asyncio.gather()
는 "모든 것을 동시에 실행하고, 모든 결과가 필요해!"라는 경우에 적합하고, asyncio.wait()
는 "이것들 중에 뭐라도 먼저 끝나거나, 일정 시간 내에 뭐가 되는지 보고 싶어!"라는 경우에 더 적합합니다. Python 3.11부터는 asyncio.TaskGroup
이라는 더 안전하고 구조화된 동시성 API도 도입되어, 많은 경우 gather
와 wait
의 대안으로 사용될 수 있습니다.
728x90
반응형