asyncio로 개발하기¶
비동기 프로그래밍은 고전적인 “순차적” 프로그래밍과 다릅니다.
이 페이지는 흔한 실수와 함정을 나열하고, 이를 피하는 방법을 설명합니다.
디버그 모드¶
기본적으로 asyncio는 프로덕션 모드로 실행됩니다. 개발을 쉽게 하려고 asyncio에는 디버그 모드를 제공합니다.
여러 가지 방법으로 asyncio 디버그 모드를 활성화할 수 있습니다:
PYTHONASYNCIODEBUG
환경 변수를1
로 설정.-X
dev
파이썬 명령 줄 옵션 사용.debug=True
를asyncio.run()
로 전달.loop.set_debug()
를 호출.
디버그 모드를 활성화하는 것 외에도, 다음을 고려하십시오:
asyncio 로거의 로그 수준을
logging.DEBUG
로 설정, 예를 들어 응용 프로그램 시작 시 다음 코드 조각을 실행할 수 있습니다:logging.basicConfig(level=logging.DEBUG)
ResourceWarning
경고를 표시하도록warnings
모듈을 구성. 이렇게 하는 한 가지 방법은-W
default
명령 줄 옵션을 사용하는 것입니다.
디버그 모드가 활성화되면:
asyncio는 기다리지 않은 코루틴을 검사하고 로그 합니다; 이것은 “잊힌 await” 함정을 완화합니다.
많은 스레드 안전하지 않은 asyncio API(
loop.call_soon()
과loop.call_at()
메서드와 같은)가 잘못된 스레드에서 호출될 때 예외를 발생시킵니다.I/O 선택기의 실행 시간은 I/O 연산 수행에 너무 오래 걸리면 로그 됩니다.
100ms보다 오래 걸리는 콜백이 로그 됩니다.
loop.slow_callback_duration
어트리뷰트는 “느린” 것으로 간주할 최소 실행 시간(초)을 설정하는 데 사용될 수 있습니다.
동시성과 다중 스레드¶
이벤트 루프는 스레드(일반적으로 주 스레드)에서 실행되며 그 스레드에서 모든 콜백과 태스크를 실행합니다. 태스크가 이벤트 루프에서 실행되는 동안, 다른 태스크는 같은 스레드에서 실행될 수 없습니다. 태스크가 await
표현식을 실행하면, 실행 중인 태스크가 일시 중지되고 이벤트 루프는 다음 태스크를 실행합니다.
다른 OS 스레드에서 콜백을 예약하려면, loop.call_soon_threadsafe()
메서드를 사용해야 합니다. 예:
loop.call_soon_threadsafe(callback, *args)
거의 모든 asyncio 객체는 스레드 안전하지 않습니다. 태스크나 콜백 외부에서 작동하는 코드가 없으면 일반적으로 문제가 되지 않습니다. 그러한 코드가 저수준 asyncio API를 호출해야 하면, loop.call_soon_threadsafe()
메서드를 사용해야 합니다, 예를 들어:
loop.call_soon_threadsafe(fut.cancel)
다른 OS 스레드에서 코루틴 객체를 예약하려면, run_coroutine_threadsafe()
함수를 사용해야 합니다. 결과에 액세스할 수 있도록 concurrent.futures.Future
를 반환합니다:
async def coro_func():
return await asyncio.sleep(1, 42)
# Later in another OS thread:
future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
# Wait for the result:
result = future.result()
시그널을 처리하고 자식 프로세스를 실행하려면, 이벤트 루프를 메인 스레드에서 실행해야 합니다.
loop.run_in_executor()
메서드는 concurrent.futures.ThreadPoolExecutor
와 함께 사용되어, 이벤트 루프가 실행되는 OS 스레드를 블록하지 않고 다른 OS 스레드에서 블로킹 코드를 실행할 수 있습니다.
블로킹 코드 실행하기¶
블로킹 (CPU 병목) 코드는 직접 호출하면 안 됩니다. 예를 들어, 함수가 CPU 집약적인 계산을 1초 동안 수행하면, 모든 동시 asyncio 태스크와 IO 연산이 1초 지연됩니다.
An executor can be used to run a task in a different thread or even in
a different process to avoid blocking the OS thread with the
event loop. See the loop.run_in_executor()
method for more
details.
로깅¶
asyncio는 logging
모듈을 사용하고, 모든 로깅은 "asyncio"
로거를 통해 수행됩니다.
기본 로그 수준은 logging.INFO
며, 쉽게 조정할 수 있습니다:
logging.getLogger("asyncio").setLevel(logging.WARNING)
await 하지 않은 코루틴 감지¶
코루틴 함수가 호출되었지만 기다리지 않을 때(예를 들어, await coro()
대신 coro()
)나 코루틴이 asyncio.create_task()
로 예약되지 않으면 asyncio가 RuntimeWarning
을 방출합니다:
import asyncio
async def test():
print("never scheduled")
async def main():
test()
asyncio.run(main())
출력:
test.py:7: RuntimeWarning: coroutine 'test' was never awaited
test()
디버그 모드에서의 출력:
test.py:7: RuntimeWarning: coroutine 'test' was never awaited
Coroutine created at (most recent call last)
File "../t.py", line 9, in <module>
asyncio.run(main(), debug=True)
< .. >
File "../t.py", line 7, in main
test()
test()
일반적인 수정법은 코루틴을 await 하거나 asyncio.create_task()
함수를 호출하는 것입니다:
async def main():
await test()
전달되지 않은 예외 감지¶
Future.set_exception()
가 호출되었지만, Future 객체가 await 되지 않으면, 예외는 절대로 사용자 코드로 전파되지 않습니다. 이럴 때, Future 객체가 가비지 수집될 때 asyncio가 로그 메시지를 출력합니다.
처리되지 않은 예외의 예:
import asyncio
async def bug():
raise Exception("not consumed")
async def main():
asyncio.create_task(bug())
asyncio.run(main())
출력:
Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3>
exception=Exception('not consumed')>
Traceback (most recent call last):
File "test.py", line 4, in bug
raise Exception("not consumed")
Exception: not consumed
태스크가 만들어진 곳의 트레이스백을 얻으려면 디버그 모드를 활성화하세요:
asyncio.run(main(), debug=True)
디버그 모드에서의 출력:
Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3>
exception=Exception('not consumed') created at asyncio/tasks.py:321>
source_traceback: Object created at (most recent call last):
File "../t.py", line 9, in <module>
asyncio.run(main(), debug=True)
< .. >
Traceback (most recent call last):
File "../t.py", line 4, in bug
raise Exception("not consumed")
Exception: not consumed