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

[PYTHON] 비동기 프로그래밍의 핵심, Future와 Task 객체의 3가지 결정적 차이 및 활용 방법

by Papa Martino V 2026. 3. 29.
728x90

Future와 Task 객체
Future와 Task 객체

 

파이썬 asyncio의 내부 매커니즘을 파헤치고 실무 비동기 코드를 최적화하는 전문가 가이드


1. 파이썬 비동기 생태계의 기초: 왜 Future와 Task인가?

파이썬 3.4 이후 도입된 asyncio 라이브러리는 현대 백엔드 개발의 패러다임을 바꾸었습니다. 특히 대규모 입출력(I/O) 바운드 작업을 처리할 때 싱글 스레드만으로도 높은 동시성을 확보할 수 있게 되었습니다. 이 비동기 프로그래밍의 중심에는 '아직 완료되지 않은 작업'을 추상화한 두 가지 객체, FutureTask가 존재합니다. 많은 개발자가 이 두 객체를 혼용하거나 정확한 차이점을 인지하지 못한 채 사용하곤 합니다. 하지만 효율적인 리소스 관리와 복잡한 비동기 흐름 제어를 위해서는 이들의 계층적 구조와 상태 관리 방식을 이해하는 것이 필수적입니다.

2. Future vs Task: 심층 비교 분석

Future는 결과의 저장소 역할을 하며, Task는 코루틴을 실행하는 능동적인 실행 주체입니다. 이들의 차이를 표를 통해 한눈에 비교해 보겠습니다.

비교 항목 Future (asyncio.Future) Task (asyncio.Task)
정의 작업의 최종 결과를 담는 저수준 객체 코루틴의 실행을 관리하는 고수준 객체
상속 관계 기본 클래스 (Base Class) Future를 상속받은 하위 클래스
생성 방식 loop.create_future() asyncio.create_task(coro)
실행 주체 외부에서 결과값을 수동으로 설정 (set_result) 이벤트 루프가 자동으로 코루틴을 실행
사용 수준 라이브러리/프레임워크 구현 시 주로 사용 일반적인 애플리케이션 비즈니스 로직 작성 시 사용
상태 제어 결과 대기 및 콜백 등록 취소(Cancel), 진행 상태 확인, 결과 반환

3. 실무 중심의 7가지 개발자 Sample Examples

이론을 넘어 실제 프로젝트에서 Future와 Task를 어떻게 다루는지 7가지 핵심 예제를 통해 살펴봅니다.

Example 1: Task를 활용한 비동기 함수 병렬 실행

가장 흔한 패턴으로, 여러 코루틴을 Task로 감싸 이벤트 루프에 등록하고 동시에 실행하는 방법입니다.


import asyncio

async def fetch_data(id):
    print(f"Task {id} 시작")
    await asyncio.sleep(1)
    return f"Data {id} 완료"

async def main():
    # Task 생성 및 예약
    task1 = asyncio.create_task(fetch_data(1))
    task2 = asyncio.create_task(fetch_data(2))
    
    # 작업 완료 대기
    res1 = await task1
    res2 = await task2
    print(res1, res2)

asyncio.run(main())
        

Example 2: Future 객체 수동 제어 (저수준 구현)

외부 이벤트나 특정 조건이 만족되었을 때 비동기 루프에 신호를 주는 저수준 방식입니다.


import asyncio

def external_event_handler(fut):
    print("외부 이벤트 발생! 결과를 설정합니다.")
    fut.set_result("Success from External Source")

async def main():
    loop = asyncio.get_running_loop()
    fut = loop.create_future()
    
    # 2초 뒤 외부 핸들러 실행 시뮬레이션
    loop.call_later(2, external_event_handler, fut)
    
    print("결과를 기다리는 중...")
    result = await fut
    print(f"받은 결과: {result}")

asyncio.run(main())
        

Example 3: 실행 중인 Task의 취소 및 예외 처리

오래 걸리는 작업이 필요 없어졌을 때 Task를 안전하게 중단시키는 방법입니다.


import asyncio

async def long_running_job():
    try:
        await asyncio.sleep(10)
    except asyncio.CancelledError:
        print("작업이 정상적으로 취소되었습니다.")
        raise

async def main():
    task = asyncio.create_task(long_running_job())
    await asyncio.sleep(1)
    task.cancel()  # 작업 취소 요청
    
    try:
        await task
    except asyncio.CancelledError:
        print("Main: Task 취소 예외 포착")

asyncio.run(main())
        

Example 4: asyncio.gather를 이용한 다중 Task 관리

수많은 Task의 결과를 한꺼번에 수집할 때 사용하며, 내부적으로는 Task 리스트를 Future로 변환하여 처리합니다.


import asyncio

async def job(n):
    await asyncio.sleep(n)
    return n * 10

async def main():
    tasks = [job(i) for i in range(1, 4)]
    # 여러 Task를 동시에 실행하고 결과 취합
    results = await asyncio.gather(*tasks)
    print(f"결과 리스트: {results}")

asyncio.run(main())
        

Example 5: Future에 콜백 함수 등록하기 (add_done_callback)

await를 사용하지 않고 작업이 끝나는 시점에 특정 로직을 실행해야 할 때 유용합니다.


import asyncio

def notify(fut):
    print(f"알림: 작업 완료! 결과는 {fut.result()} 입니다.")

async def main():
    task = asyncio.create_task(asyncio.sleep(1, result="Done"))
    task.add_done_callback(notify)
    await task

asyncio.run(main())
        

Example 6: Task Group을 활용한 안전한 비동기 제어 (Python 3.11+)

최신 파이썬 버전에서 Task들을 구조적으로 관리하여 예외 발생 시 모든 Task를 자동으로 취소하는 최신 기법입니다.


import asyncio

async def sub_task(name, delay):
    await asyncio.sleep(delay)
    print(f"Task {name} 완료")

async def main():
    async with asyncio.TaskGroup() as tg:
        tg.create_task(sub_task("A", 1))
        tg.create_task(sub_task("B", 2))
    print("모든 Task Group 작업 종료")

asyncio.run(main())
        

Example 7: 동기 함수를 Future로 래핑하여 비동기처럼 사용하기

CPU 집약적인 작업이나 블로킹 I/O를 run_in_executor를 통해 Future 객체로 다루는 예제입니다.


import asyncio
import time
from concurrent.futures import ThreadPoolExecutor

def blocking_io():
    time.sleep(2)
    return "IO 작업 완료"

async def main():
    loop = asyncio.get_running_loop()
    with ThreadPoolExecutor() as pool:
        # 동기 함수를 Executor에서 실행하여 Future 반환
        result = await loop.run_in_executor(pool, blocking_io)
        print(result)

asyncio.run(main())
        

4. 결론: 개발자가 기억해야 할 핵심 포인트

파이썬 비동기 프로그래밍에서 Future는 결과값에 대한 약속이며, Task는 그 약속을 지키기 위해 실제로 발로 뛰는 일꾼입니다. 대부분의 실무 애플리케이션 개발 단계에서는 asyncio.create_task()를 통해 코루틴을 관리하는 것으로 충분합니다. 하지만 저수준 통신 라이브러리를 개발하거나 복잡한 이벤트 핸들링이 필요한 경우에는 Future 객체를 직접 조작하는 능력이 고수준 개발자의 차이를 만듭니다.

내용 출처 및 참고 자료

  • Python 공식 문서 (asyncio - Tasks and Futures): https://docs.python.org/3/library/asyncio-task.html
  • Fluent Python 2nd Edition (Luciano Ramalho 저) - Chapter 21: Asynchronous Programming
  • Python Software Foundation - Asyncio Internal Design Documentation
728x90