비동기 함수가 바로 리턴 해버리면서 완료 되지 않은 결과에 접근하는 것을 방지하기 위해
비동기 함수 호출 앞에 await 키워드를 사용
await 키워드를 사용하면 요청 했던 비동기 작업의 완료 통보가 올 때까지 대기
다음으로 바로 진행하지 않고 완료 통보가 올 때 까지 진행
import asyncio
import time
async def get_num(num):
await asyncio.sleep(5)
print(f"{num} 접근")
async def main() :
await get_num(1)
await get_num(2)
await get_num(3)
await get_num(4)
await get_num(5)
await get_num(6)
asyncio.run(main()) # main 함수 실행
위 코드는 비동기로 처리되지 않으며, 매 task 다 5초 sleep이 실행되어 5*6=30초 이상 걸린다.
이는 event loop에 6개의 task가 등록되지 않아서 다 따로 처리된 것임
import asyncio
import time
async def get_num(num):
await asyncio.sleep(5)
print(f"{num} 접근")
async def main() :
# using create_task
task1 = asyncio.create_task(get_num(1))
task2 = asyncio.create_task(get_num(2))
task3 = asyncio.create_task(get_num(3))
task4 = asyncio.create_task(get_num(4))
task5 = asyncio.create_task(get_num(5))
task6 = asyncio.create_task(get_num(6))
await task1
await task2
await task3
await task4
await task5
await task6
asyncio.run(main()) # main 함수 실행
위 코드는 5초에 모든 task가 완료 된다.
create_task() 를 이용해 모든 task를 event loop에 등록
실행 순서는 1, 3, 6, 5, 2, 4이며, 비동기 처리로 실행 순서는 보장되지 않는다.
create_task() 말고 asyncio.gather() 사용해도 동일
# using gather
await asyncio.gather(
get_num(1),
get_num(2),
get_num(3),
get_num(4),
get_num(5),
get_num(6)
)
만약 await asyncio.sleep(5) 가 time.sleep(5)여도 비동기로 처리될까?
→ time.sleep(5)는 동기로 처리되며, 바꿨을 경우 5*6=30초의 실행 시간이 걸리게 된다.
그렇다면 get_num() 함수에서 print를 2번 한다면, 결과는 어떨까?
import asyncio
import time
async def get_num(num):
await asyncio.sleep(5)
print(f"{num} 1번 째로 접근")
print(f"{num} 2번 째로 접근")
async def main() :
# using create_task
task1 = asyncio.create_task(get_num(1))
task2 = asyncio.create_task(get_num(2))
task3 = asyncio.create_task(get_num(3))
task4 = asyncio.create_task(get_num(4))
task5 = asyncio.create_task(get_num(5))
task6 = asyncio.create_task(get_num(6))
await task1
await task2
await task3
await task4
await task5
await task6
asyncio.run(main()) # main 함수 실행
1 1번 째로 접근
1 2번 째로 접근
3 1번 째로 접근
3 2번 째로 접근
6 1번 째로 접근
6 2번 째로 접근
5 1번 째로 접근
5 2번 째로 접근
2 1번 째로 접근
2 2번 째로 접근
4 1번 째로 접근
4 2번 째로 접근
처음엔 task 1~6과 각 task 내부에 존재하는 2개의 print가 모두 비동기로 처리 돼 뒤죽박죽 나올 것을 예상
print는 동기 함수이기 때문에 순차적으로 출력된 것
만약 get_num 내부 함수가 print가 아니라 다른 task고, 그 안에 또 다른 event loop이 만들어 진다면,
task1의 get_num() 내부 함수들이 비동기로 처리되고 → 모두 마치면 task2로 넘어갈 것
⇒ 즉 비동기 처리 함수 내부의 비동기 처리를 원하는 만큼 깊게 쌓을 수 있음
get_running_loop
get_event_loop 과 비슷하나 해당 함수는 파이썬 3.10 이상부터 deprecate
현재 실행 중인 이벤트 루프를 반환
run_in_executor(executor, func, *args)
지정된 executor에서 func가 호출되도록 등록
*args는 func 함수에 사용될 인자를 의미
동기 함수인 func를 비동기로 처리할 수 있게 해줌
import asyncio
import time
async def get_num(num):
await asyncio.sleep(5)
time.sleep(5)
print(f"{num} 접근")
async def main() :
# using create_task
task1 = asyncio.create_task(get_num(1))
task2 = asyncio.create_task(get_num(2))
task3 = asyncio.create_task(get_num(3))
task4 = asyncio.create_task(get_num(4))
task5 = asyncio.create_task(get_num(5))
task6 = asyncio.create_task(get_num(6))
await task1
await task2
await task3
await task4
await task5
await task6
asyncio.run(main()) # main 함수 실행
위 코드는 task 1~6은 비동기로 실행 되나, time.sleep(5) 가 동기로 처리되어
task 1~6을 비동기로 처리하는 시간 5초 + 각 task의 time.sleep(5) 5*6=30초 = 35초가 걸린다.
실행 결과는 위와 동일하게 1, 3, 6, 5, 2, 4가 출력
import asyncio
import time
async def get_num(num):
await asyncio.sleep(5)
loop = asyncio.get_running_loop() # 이벤트 루프 객체 얻기
await loop.run_in_executor(None, time.sleep, 5)
print(f"{num} 접근")
async def main() :
# using create_task
task1 = asyncio.create_task(get_num(1))
task2 = asyncio.create_task(get_num(2))
task3 = asyncio.create_task(get_num(3))
task4 = asyncio.create_task(get_num(4))
task5 = asyncio.create_task(get_num(5))
task6 = asyncio.create_task(get_num(6))
await task1
await task2
await task3
await task4
await task5
await task6
asyncio.run(main()) # main 함수 실행
동기 함수인 time.sleep 을 run_in_executor 이용해 비동기로 처리
실행 시간은 task 1~6 비동기 처리 5초 + sleep 비동기 처리 5초 = 10초
실행 결과는 위와 다르게 1, 3, 6, 5 ,2, 4가 아니며, 실행 할 때마다 매번 다른 순서로 출력
⇒ 실행 시간이 긴 동기 함수를 비동기로 처리하고 싶을 때 (e.g., requests.get(url)) 사용