python - async def, asyncio
async def
비동기 함수를 정의하기 위한 파이썬 문법. async def 키워드를 사용하여 정의된 함수는 비동기적으로 실행될 수 있고 내부의 비동기 함수도 병렬로 실행할 수 있다
async def convert_int(a: str):
await asyncio.sleep(1)
return int(a)
async def calc_add(str_a: str, str_b: str):
now = datetime.now()
int_a, int_b = await asyncio.gather(convert_int(str_a), convert_int(str_b))
print(datetime.now() - now) # 0:00:01.001219
return int_a + int_b
loop = asyncio.get_event_loop()
loop.run_until_complete(calc_add('1', '2'))
loop.close()
# 출력 결과
excution time 0:00:01.001146
calc_add 는 두개의 문자열의 합을 리턴하는 함수고, convert_int 는 문자열 값을 숫자로 캐스팅하는 함수인데 1초 딜레이를 걸어줬다. 매개변수 str_a str_b 에 대해서 각각 convert_int 함수를 순차적으로 실행했다면 2초가 걸렸을 것인데 asyncio gather 함수를 이용해 병렬로 실행했기 때문에 총 1초가 소요되었다
async def 함수는 asyncio 라이브러리와 결합해서 많이 사용한다. asyncio 를 먼저 살펴보자
asyncio
파이썬에서 비동기 프로그래밍을 지원하기위한 도구와 이벤트 루프를 제공하는 라이브러리. async def 로 정의된 비동기 함수를 실행하고 내부 비동기 로직을 관리하는데 사용한다.
이벤트 루프는 비동기 작업을 실행하고 관리하는 핵심 요소. 비동기 코드의 실행 흐름을 제어아고 스케줄링 이벤트처리, 타이머를 관리한다. 실질적으로 비동기 코드를 실행하는 주체라고 보면 쉽다.
예제 코드에서는 asyncio 라이브러리로부터 이벤트루프를 받고 실행할 함수를 인자로 전달했다
loop = asyncio.get_event_loop()
loop.run_until_complete(calc_add('1', '2'))
loop.close()
- get_event_loop : 현재 실행중인 쓰레드에서 이벤트 루프를 가져온다. 없으면 새로 생성한다
- run_until_complete : 함수의 인자로 넣은 코드가 종료될 때 까지 실행한다
- close : 이벤트 루프를 종료한다.
run 함수를 이용해서 좀 더 간단히 표현할 수 있다. 그런데 run 함수는 동일한 쓰레드에 이미 이벤트 루프를 실행중이면 사용할 수 없으니 호출할 때 주의해야 한다.
asyncio.run(calc_add("1", "2"))
gather
여러 개의 비동기 작업을 동시에 실행하고 결과를 한꺼번에 모으는 함수. 작업을 병렬로 실행하며 모든 작업이 완료할 떄 까지 대기한 후 결과를 반환한다.
int_a, int_b = await asyncio.gather(convert_int(str_a), convert_int(str_b))
print("gather done") # asyncio.gather() 종료후에 실행
앞의 예제에선 gather 함수를 이용해서 각 문자열을 캐스팅 하는 작업을 동시에 처리했다. 그래서 2초가 아니라 1초만에 결과값을 받을 수 있었다. 여러 작업을 동시에 처리하고 싶은 경우에는 gather 함수를 사용하면 유용하다
create_task
작업을 생성하고 상태를 추적할 수 있는 기능을 제공하는 함수다. 리턴받는 Task 객체를 이용해서 상태를 지속적으로 확인할 수 있다
async def request_somewhere():
await asyncio.sleep(4)
return "result"
async def request_handle():
task = asyncio.create_task(request_somewhere())
while True:
if task.done():
print("finally ready")
print(task.result())
break
else:
await asyncio.sleep(1)
print("task is running")
asyncio.run(request_handle())
# 출력결과
task is running
task is running
task is running
task is running
finally ready
result
만약 작업이 너무 오래 걸리고 있다면 cancel 함수를 이용해서 작업을 취소할 수도 있다.
async def request_handle():
task = asyncio.create_task(request_somewhere())
sleep_count = 0
while True:
if task.done():
print("finally ready")
print(task.result())
break
elif sleep_count > 3:
print("can't wait anymore, cancel!")
task.cancel()
break
else:
await asyncio.sleep(1)
print("task is running")
sleep_count += 1
# 출력 결과
task is running
task is running
task is running
task is running
can't wait anymore, cancel!
이외에도 다양한 라이브러리가 많으니 python 에서 비동기를 처리할 때 적절하게 사용하도록 하자