asyncio는 async/await 구문을 사용하여 동시성 코드를 작성하는 파이썬의 기본 라이브러리이다.
asyncio는 고성능 네트워크 및 웹 서버, 데이터베이스 연결 라이브러리, 분산 작업 큐 등을 제공하는 여러 파이썬 비동기 프레임워크의 기반으로 사용된다.
asyncio는 I/O 바운드이면서 고수준의 구조화된 네트워크 코드에 적합하다.
import asyncio
async def main():
print('hello')
await asyncio.sleep(1)
print('world')
asyncio.run(main()) # hello 출력 -> 1초 대기 -> world 출력
main()
<coroutine object main at 0x1053bb7c8>
최상위 진입점 main()
함수를 실행하는 asyncio.run()
함수 (위 예시 코드의 main)
async def 함수
안에서 await 코루틴
사용.
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
# 다음 코드는 1초를 기다린 후 “hello”를 프린트한 다음 또 2초를 기다린 후 “world”를 프린트한다.
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
예상 출력:
started at 17:13:52
hello
world
finished at 17:13:55
asyncio.create_task()
함수 사용.async def main():
task1 = asyncio.create_task(
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
started at 17:14:32
hello
world
finished at 17:14:34
객체가 await 표현식에서 사용될 수 있으면 어웨이터블(awaitable) 객체라고 한다.
많은 asyncio API는 어웨이터블을 받아들이도록 설계되었다.
어웨이터블 객체에는 코루틴, 태스크, 퓨처의 세 가지 유형이 있다.
import asyncio
async def nested():
return 42
async def main():
# nested()
# 이런식으로 호출하면 아무 일도 일어나지 않는다.
# await 되지 않았기 때문이다.
print(await nested()) # 42 출력
asyncio.run(main())
코루틴
이라는 용어는 두 가지의 밀접한 개념에 관련이 있다.async def 함수명
태스크는 코루틴을 동시에 예약하는 데 사용된다.
코루틴을 asyncio.create_task()
와 같은 함수를 사용하여 태스크로 넣으면 코루틴이 곧 실행되도록 자동으로 예약된다.
import asyncio
async def nested():
return 42
async def main():
# nested()를 main()에서 동시적으로 실행하도록 해준다.
task = asyncio.create_task(nested())
# 이제 task를 통해 nested()를 취소하거나 nested()가 완료될 때까지 기다릴 수 있다.
await task
asyncio.run(main())
퓨처는 비동기 연산의 최종 결과를 나타내는 특별한 저수준 어웨이터블 객체이다.
퓨처 객체를 기다릴 때, 그것은 코루틴이 퓨처가 다른 곳에서 해결될 때까지 기다릴 것을 뜻한다.
콜백 기반 코드를 async/await와 함께 사용하려면 asyncio의 퓨처 객체가 필요하다.
일반적으로 응용 프로그램 수준 코드에서 퓨처 객체를 만들 필요는 없다.
때때로 라이브러리와 일부 asyncio API에 의해 노출되는 퓨처 객체를 기다릴 수 있다:
async def main():
await function_that_returns_a_future_object()
# this is also valid:
await asyncio.gather(
function_that_returns_a_future_object(),
some_python_coroutine()
)
loop.run_in_executor()
이다.asyncio.run(coro, *, debug=False)
코루틴 coro
를 실행하고 결과를 반환한다.
이 함수는 전달된 코루틴을 실행하고 asyncio 이벤트 루프와 비동기 제너레이터의 파이널리제이션을 관리한다.
다른 asyncio 이벤트 루프가 같은 스레드에서 실행 중이면 이 함수를 호출할 수 없다.
debug=True
이면 이벤트 루프가 디버그 모드로 실행된다.
이 함수는 항상 새 이벤트 루프를 만들고 끝에 이벤트 루프를 닫는다. asyncio 프로그램의 메인 진입 지점으로 사용해야 하고, 이상적으로는 한 번만 호출해야 한다.
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
asyncio.create_task(coro, *, name=None)
coro 코루틴을 Task로 감싸고 실행을 예약한다. Task 객체를 반환한다.
name이 None이 아니면, Task.set_name()
을 사용하여 태스크의 이름으로 설정된다.
get_running_loop()
에 의해 반환된 루프에서 태스크가 실행되고, 현재 스레드에 실행 중인 루프가 없으면 RuntimeError가 발생한다.
이 함수는 파이썬 3.7에서 추가되었다. 파이썬 3.7 이전 버전에서는, 대신 저수준의 asyncio.ensure_future()
함수를 사용할 수 있다.
async def coro():
...
# In Python 3.7+
task = asyncio.create_task(coro())
...
# This works in all Python versions but is less readable
task = asyncio.ensure_future(coro())
...
coroutine asyncio.sleep(delay, result=None, *, loop=None)
delay(초) 동안 블록한다.
파이썬 time 모듈의 time.sleep()
과 비슷한데, time.sleep()
은 어웨이터블 객체가 아니라서 await로 쓸 수 없다.
result 인수가 있으면 코루틴이 완료될 때 호출자에게 result를 반환한다.
sleep()
은 항상 현재 태스크를 일시 중단해서 다른 태스크를 실행할 수 있도록 한다.
loop 매개변수는 파이썬 3.8 부터 지원이 중단되며 3.10부터는 삭제될 예정이다.
5초 동안 현재시간을 매초 표시하는 코루틴의 예:
import asyncio
import datetime
async def display_date():
loop = asyncio.get_running_loop()
end_time = loop.time() + 5.0
while True:
print(datetime.datetime.now())
if (loop.time() + 1.0) >= end_time:
break
await asyncio.sleep(1)
asyncio.run(display_date())
awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)
aws 시퀀스에 있는 어웨이터블 객체를 동시에 실행한다.
aws에 있는 어웨이터블이 코루틴이면 자동으로 태스크로 예약된다.
모든 어웨이터블이 성공적으로 완료되면, 반환된 값들이 합쳐진 리스트가 반환된다. 결괏값의 순서는 aws에 있는 어웨이터블의 순서와 일치한다.
return_exceptions가 False(기본값)면, 첫 번째 발생한 예외가 gather()
를 기다리는 태스크로 즉시 전파된다. aws 시퀀스의 다른 어웨이터블은 취소되지 않고 계속 실행된다.
return_exceptions가 True면, 예외는 성공적인 결과처럼 처리되고, 결과 리스트에 집계된다.
gather()
가 취소되면, 모든 제출된 (아직 완료되지 않은) 어웨이터블도 취소된다.
aws 시퀀스의 Task나 Future가 취소되면, 그것이 CancelledError를 일으킨 것처럼 처리된다 – 이때 gather()
호출은 취소되지 않는다. 이것은 제출된 태스크/퓨처 하나를 취소하는 것이 다른 태스크/퓨처를 취소하게 되는 것을 막기 위한 것이다.
loop 매개변수는 파이썬 3.8 부터 지원이 중단되며 3.10부터는 삭제될 예정이다.
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({i})...")
await asyncio.sleep(1)
f *= i
print(f"Task {name}: factorial({number}) = {f}")
async def main():
# Schedule three calls *concurrently*:
await asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
)
asyncio.run(main())
# Expected output:
#
# Task A: Compute factorial(2)...
# Task B: Compute factorial(2)...
# Task C: Compute factorial(2)...
# Task A: factorial(2) = 2
# Task B: Compute factorial(3)...
# Task C: Compute factorial(3)...
# Task B: factorial(3) = 6
# Task C: Compute factorial(4)...
# Task C: factorial(4) = 24
awaitable asyncio.shield(aw)
task = asyncio.create_task(something())
res = await shield(task)
위 코드는 다음과 같다.
res = await something()
단, 해당 코루틴이 포함된 코루틴이 취소된 경우 something()
에서 실행 중인 Task는 취소되지 않는다. something()
의 관점에서 취소는 발생하지 않는다. 호출자가 취소된 경우에도 await
표현식을 사용하면 CancelledError
가 발생한다.
something()
이 다른 수단(즉, 자기 자신에 의해)에 의해 취소되면, shield()
도 취소된다.
취소를 완전히 무시하려면(권장하지 않음) 다음과 같이 shield()
함수를 try/except 절과 결합해야 한다.
task = asyncio.create_task(something())
try:
res = await shield(task)
except CancelledError:
res = None
loop
매개변수가 제거됨.asyncio.timeout(delay)
뭔가를 기다리는 데 소요되는 시간을 제한하기 위해 사용할 수 있는 비동기 컨텍스트 관리자를 반환한다.
delay
는 None 또는 float/int 초의 대기 시간이 될 수 있다.
delay가 None이면 시간 제한이 적용되지 않는다. 이는 컨텍스트 관리자가 생성될 때 delay를 알 수 없는 경우에 유용할 수 있다.
None 또는 None이 아닌 경우 모두 밑에서 설명할 Timeout.reschedule()
을 사용하여 이미 생성된 컨텍스트 관리자를 재설정할 수 있다.
async def main():
async with asyncio.timeout(10):
await long_running_task()
asyncio.CancelledError
를 처리하여 포착하고 처리할 수 있는 TimeoutError
로 변환한다.asyncio.timeout()
컨텍스트 관리자는 asyncio.CanceledError
를 TimeoutError
로 변환하는데, 이는 TimeoutError
는 컨텍스트 관리자의 외부에서만 포착될 수 있음을 의미한다.TimeoutError
을 잡아내는 예시 코드async def main():
try:
async with asyncio.timeout(10):
await long_running_task()
except TimeoutError:
print("The long operation timed out, but we've handled it.")
print("This statement will run regardless.")
asyncio.timeout()
에 의해 생성된 컨텍스트 관리자는 다른 기한으로 재설정되고 검사될 수 있다class asyncio.Timeout(when)
when
은 컨텍스트가 시간 초과되어야 하는 절대 시간이어야 한다. 이 시간은 이벤트 루프의 시계로 측정된다.when() → float | None
reschedule(when: float | None)
expired() → bool
async def main():
try:
# We do not know the timeout when starting, so we pass ``None``.
async with asyncio.timeout(None) as cm:
# We know the timeout now, so we reschedule it.
new_deadline = get_running_loop().time() + 10
cm.reschedule(new_deadline)
await long_running_task()
except TimeoutError:
pass
if cm.expired():
print("Looks like we haven't finished on time.")
asyncio.timeout_at(when)
asyncio.timeout()
과 유사하지만 when
이 대기를 중지할 절대 시간이거나 None
이라는 점이 다르다.async def main():
loop = get_running_loop()
deadline = loop.time() + 20
try:
async with asyncio.timeout_at(deadline):
await long_running_task()
except TimeoutError:
print("The long operation timed out, but we've handled it.")
print("This statement will run regardless.")
coroutine asyncio.wait_for(aw, timeout)
aw
가 대기 시간이 초과되어 완료될 때까지 기다린다. timeout
은 None 또는 float/int 초의 대기 시간이 될 수 있다.timeout
이 None이면 미래에 수행될 작업이 완료될 때까지 블록한다.TimeoutError
를 발생시킨다.asyncio.shield()
로 감싸면 된다.aw
도 취소된다.aw
가 취소되면 wait_for 함수는 aw가 취소될 때까지 기다린다. 이전에는 TimeoutError
가 즉시 발생했다.loop
매개변수가 제거됨.async def eternity():
# Sleep for one hour
await asyncio.sleep(3600)
print('yay!')
async def main():
# Wait for at most 1 second
try:
await asyncio.wait_for(eternity(), timeout=1.0)
except TimeoutError:
print('timeout!')
asyncio.run(main())
# Expected output:
#
# timeout!