파이썬 비동기: 작업 취소로부터 보호하기

asyncio의 Task 객체는 cancel() 메서드를 통해 실행을 중단할 수 있습니다. 특정 작업이 중요한 경우, asyncio.shield() 함수를 사용하여 해당 작업이 외부에서 취소되는 것을 방지할 수 있습니다.

asyncio.shield()란?

asyncio.shield()는 Awaitable 객체를 감싸는 Future를 생성하며, 이 Future는 취소 요청을 흡수합니다. 즉, shield로 래핑된 작업은 다른 코드에 의해 취소되었더라도 내부적으로 계속 실행됩니다.

이 기능은 일부 작업만 선택적으로 취소하고 싶은 상황에서 유용합니다. 예를 들어 중요한 백그라운드 작업은 유지하면서 덜 중요한 작업만 종료해야 할 때 활용할 수 있습니다.

사용 방법

asyncio.shield()는 하나의 Awaitable 객체를 인자로 받아 Future 객체를 반환합니다:

# 작업 보호
protected_future = asyncio.shield(some_task)

# 대기
await protected_future

반환된 Future는 cancel() 호출 시 성공 여부를 반환하지만 실제 작업에는 영향을 미치지 않습니다:

# 보호된 Future 취소 시도
is_cancelled = protected_future.cancel()

Future를 대기하는 동안 CancelledError 예외가 발생할 수 있으므로 적절한 처리가 필요합니다:

try:
    result = await asyncio.shield(background_work())
except asyncio.CancelledError:
    print("보호된 작업 대기 중 취소됨")

중요한 점은 Future에 대한 취소 요청이 원본 작업으로 전파되지 않는다는 것입니다. 반면, 원본 작업 자체가 취소되면 shield도 함께 취소됩니다:

# 원본 작업 생성
original_task = asyncio.create_task(worker())

# 보호
guarded = asyncio.shield(original_task)

# shield만 취소 - 원본은 유지
guarded.cancel()

# 원본 작업 직접 취소 - shield도 함께 취소됨
original_task.cancel()

실제 예제

다음 예제에서는 중요한 작업을 shield로 보호하고, 다른 작업이 이를 취소하려는 시나리오를 구현합니다:

import asyncio

async def important_job(value):
    await asyncio.sleep(1)
    return value * 2

async def canceller(target):
    await asyncio.sleep(0.2)
    success = target.cancel()
    print(f"취소 요청 결과: {success}")

async def main():
    job_coro = important_job(5)
    real_task = asyncio.create_task(job_coro)
    safe_task = asyncio.shield(real_task)

    # 다른 태스크가 safe_task를 취소하도록 함
    asyncio.create_task(canceller(safe_task))

    try:
        outcome = await safe_task
        print(f"결과 받음: {outcome}")
    except asyncio.CancelledError:
        print("Shield가 취소되었지만...")
    
    await asyncio.sleep(1)
    
    print(f"Shield 상태: {safe_task}")
    print(f"실제 작업 상태: {real_task}")

asyncio.run(main())

실행 결과:

취소 요청 결과: True
Shield가 취소되었지만...
Shield 상태: <Future cancelled>
실제 작업 상태: <Task finished name='Task-2' coro=<important_job() done, defined at ...> result=10>

예제에서 확인할 수 있듯이 shield는 취소되었지만 내부 작업은 정상적으로 완료되었습니다. 이처럼 asyncio.shield()는 중요한 비동기 작업을 외부의 취소 요청으로부터 보호하는 데 효과적인 도구입니다.

태그: python asyncio task-management cancellation-handling

7월 2일 19:14에 게시됨