프로그래밍/C, C++, Java, Python

Python asyncio.Runner 기준

포도알77 2026. 9. 16. 10:22

Python에서 비동기 프로그램의 최상위 진입점은 보통 asyncio.run()으로 충분합니다. 다만 같은 이벤트 루프와 같은 contextvars.Context 안에서 여러 top-level async 함수를 순서대로 실행해야 한다면 asyncio.Runner를 검토할 수 있습니다.

기준은 간단합니다. 한 번 실행하고 끝나는 CLI나 서비스 시작 코드는 asyncio.run()을 기본값으로 둡니다. 반복 호출이 필요하거나 이벤트 루프 생성 방식을 명시해야 하는 코드에서는 Runnerloop_factory를 함께 봅니다.

asyncio.run은 무엇을 해주나

Python 3.14 공식 문서 기준으로 asyncio.run(coro, *, debug=None, loop_factory=None)은 awaitable 객체를 이벤트 루프에서 실행하고 결과를 반환합니다. 실행 중에는 비동기 generator 정리와 기본 executor 종료도 처리합니다.

import asyncio

async def main():
    await asyncio.sleep(1)
    return "done"

result = asyncio.run(main())
print(result)

asyncio.run()은 같은 스레드에서 이미 다른 이벤트 루프가 실행 중이면 호출할 수 없습니다. 또 문서는 이 함수를 asyncio 프로그램의 main entry point로 쓰고, 이상적으로는 한 번만 호출하라고 안내합니다. 그래서 일반적인 스크립트나 CLI에서는 진입점 하나를 main() coroutine으로 모으는 방식이 가장 단순합니다.

Runner는 언제 필요한가

asyncio.Runner는 context manager입니다. 문서는 여러 top-level async 함수를 같은 이벤트 루프와 같은 contextvars.Context 안에서 호출해야 할 때 이 도구가 유용하다고 설명합니다.

import asyncio

async def prepare():
    await asyncio.sleep(0.1)
    return "prepared"

async def work():
    await asyncio.sleep(0.1)
    return "worked"

with asyncio.Runner() as runner:
    print(runner.run(prepare()))
    print(runner.run(work()))

위 형태는 asyncio.run()을 여러 번 호출하는 코드와 다르게 하나의 Runner context 안에서 같은 루프를 재사용합니다. Runner.run()은 awaitable을 실행하고 결과를 반환하며, coroutine이 들어오면 task로 감쌉니다. 필요한 경우 context keyword-only 인자로 별도 contextvars.Context를 지정할 수 있습니다.

run과 Runner 선택 기준

상황 권장 선택 이유
프로그램 진입점에서 async main 한 번 실행 asyncio.run() 루프 생성, 실행, 정리, 닫기를 한 번에 처리함
같은 루프에서 여러 top-level async 함수 실행 asyncio.Runner context manager 안에서 루프와 context를 함께 관리함
이미 실행 중인 이벤트 루프 안에서 호출 둘 다 직접 호출하지 않음 같은 스레드에 실행 중인 루프가 있으면 사용할 수 없음
이벤트 루프 구현을 명시적으로 바꿈 loop_factory policy 시스템 대신 명시적 루프 생성 함수를 전달함

Runner를 쓰더라도 모든 비동기 코드를 top-level에서 잘게 나눠 호출하는 습관이 좋은 것은 아닙니다. 대부분의 애플리케이션은 하나의 async main() 안에서 하위 작업을 조립하는 편이 읽기 쉽습니다. Runner는 같은 루프와 context를 공유해야 하는 구체적인 이유가 있을 때 선택하는 도구로 보는 것이 좋습니다.

loop_factory는 왜 중요해졌나

Python 3.14 문서에서 asyncio policy 시스템은 deprecated로 표시되어 있으며, Python 3.16에서 제거될 예정입니다. 정책을 전역으로 바꾸는 방식 대신 원하는 이벤트 루프 구현이 필요하면 asyncio.run() 또는 asyncio.Runnerloop_factory를 쓰라는 안내가 함께 제공됩니다.

import asyncio

async def main():
    return asyncio.get_running_loop()

loop = asyncio.run(main(), loop_factory=asyncio.new_event_loop)
print(type(loop).__name__)

loop_factory를 넘기면 그 함수가 새 이벤트 루프를 만듭니다. Runner에서 loop_factory를 쓸 때는 해당 factory가 생성한 루프를 현재 루프로 설정할 책임이 있습니다. 기본값을 쓰면 asyncio.new_event_loop()으로 루프를 만들고 asyncio.set_event_loop()로 현재 루프에 설정하는 동작을 Runner가 처리합니다.

get_event_loop과 헷갈리는 지점

coroutine이나 callback 안에서 현재 실행 중인 루프가 필요하면 asyncio.get_running_loop()가 더 명확합니다. Python 3.14 이벤트 루프 문서는 get_event_loop()의 동작이 custom policy와 얽히면 복잡해질 수 있으므로 coroutine과 callback 안에서는 get_running_loop()을 선호하라고 설명합니다.

또 Python 3.14부터 현재 이벤트 루프가 없으면 get_event_loop()RuntimeError를 발생시킵니다. 기존 코드가 암묵적으로 루프를 가져오던 방식에 기대고 있다면, 최상위 실행은 asyncio.run()으로 모으고 루프 구현 변경은 loop_factory로 명시하는 쪽이 이식성이 낫습니다.

버전 기준

asyncio.run()은 Python 3.7에 추가되었습니다. Python 3.12에서는 loop_factory 인자가 추가되었고, Python 3.14 문서 기준으로 인자로 coroutine뿐 아니라 awaitable 객체를 받을 수 있습니다.

asyncio.Runner는 Python 3.11에 추가되었습니다. Runner.run()도 Python 3.14 문서 기준으로 awaitable 객체를 받을 수 있습니다. 라이브러리 코드나 여러 Python 버전을 지원하는 프로젝트라면 이 차이를 기준으로 최소 지원 버전을 확인해야 합니다.

FAQ

asyncio.run을 여러 번 호출해도 되나?

문법적으로 항상 금지되는 것은 아니지만, 공식 문서는 main entry point로 쓰고 이상적으로 한 번만 호출하라고 안내합니다. 같은 루프와 context를 유지해야 하는 여러 top-level 호출이라면 Runner가 더 직접적인 도구입니다.

Jupyter나 이미 실행 중인 이벤트 루프 안에서 asyncio.run을 쓰면 되나?

같은 스레드에서 다른 asyncio 이벤트 루프가 이미 실행 중이면 asyncio.run()Runner.run()은 호출할 수 없습니다. 그런 환경에서는 해당 환경이 제공하는 await 방식이나 루프 연동 방식을 따라야 합니다.

이벤트 루프 policy를 새 코드에서 써도 되나?

새 코드에서는 피하는 편이 맞습니다. Python 3.14 문서는 policy 시스템을 deprecated로 표시하고 Python 3.16 제거 예정이라고 설명합니다. 루프 구현을 골라야 한다면 loop_factory를 먼저 검토합니다.

참고 문서

반응형

'프로그래밍 > C, C++, Java, Python' 카테고리의 다른 글

Python TaskGroup 기준  (0) 2026.09.18
Python eager task factory 기준  (0) 2026.09.18
Python Thread context 기준  (0) 2026.09.15
JavaScript Temporal 기준  (0) 2026.09.14
Python contextvars 사용법  (0) 2026.08.20
페이스북으로 공유카카오톡으로 공유카카오스토리로 공유트위터로 공유URL 복사