비동기

비동기 함수 정의

  • async 키워드를 함수 앞에 붙이면 비동기 함수가 됨
  • 비동기 함수를 호출하면 그 함수를 실행하는게 아니고 해당 함수에 대한 코루틴(coroutine)을 출력
  • 함수 코루틴은 그 함수를 앞으로 실행하겠다는 약속에 지나지 않음
# async1.py
import asyncio

async def add(x: int) -> int:
    return x + 1

def main():
    ret = add(1)
    print(f"ret = {ret}")

if __name__ == "__main__":
    main()
$ python async1.py
ret = <coroutine object add at 0x000001BDADBEE800>
~\Work\book\book_python\lang\async1.py:11: RuntimeWarning: coroutine 'add' was never awaited
  main()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

비동기 함수의 동기화 실행

  • 비동기 함수를 동기 함수처럼 실행하려면
    1. 비동기 함수를 호출할 때 await 키워드를 붙여서 호출하고
    2. 호출한 함수도 비동기 함수이여야 하며
    3. 최초 호출한 비동기 함수의 코루틴을 asyncio.run 함수에 인수로 넘겨야
  • 비동기 함수를 await 방식으로 실행하면 실행 중 다른 함수를 동시 실행 불가능
# async2.py
1import asyncio

async def add(x: int) -> int:
    return x + 1

2async def main():
3    ret = await add(1)
    print(f"ret = {ret}")

if __name__ == "__main__":
4    asyncio.run(main())
1
asyncio.run 함수를 사용하기 위해 asyncio 패키지 임포트
2
await 호출을 하는 main 함수도 async 키워드가 있어야 함
3
await 키워드를 붙여서 호출
4
main 함수 자체가 아니라 main 함수가 반환하는 코루틴을 asyncio.run 함수에 넣어야 함
$ python async2.py
ret = 2

task 객체

  • asyncio.create_task 함수에 비동기 함수의 코루틴을 넣어 만든 task 객체를 사용하면 동시 실행 가능
  • task 객체를 생성하는 순간 비동기 함수의 실행이 예약(스케줄링)되고 호출한 함수의 코드가 계속 실행됨
  • 다른 task 객체를 생성하면 그 비동기 함수도 실행 예약(스케줄링)
  • 호출한 함수가 await 키워드를 만나면 해당 task가 실행완료될 때까지 대기함
  • 만약 이미 완료된 상태면 바로 다음 라인으로 실행
  • 여러 task가 스케줄링되어 있는 경우 한 번에 하나의 task만 실행됨
  • task 실행중 sleep 또는 await를 만나면 해당 task는 실행을 중단하고 스케줄링된 다른 task 실행
  • 비동기 함수에는 time.sleep 함수 대신 asyncio.sleep 함수를 사용해야 함
  • asyncio.sleep 함수도 비동기 함수이므로 await 키워드로 호출해야 함
# async3.py
import asyncio

async def task_fun(x: int) -> None:
    print(f"start task{x}")
    await asyncio.sleep(x)
    print(f"finish task{x}")


async def main():
1    task = asyncio.create_task(task_fun(1))
    print(f"task = {task}")
2    ret = await task
    print(f"ret = {ret}")

if __name__ == "__main__":
    asyncio.run(main())
1
asyncio.create_task 함수로 task 객체를 만든다
2
task 개체에 대해서 await 호출
$ python async3.py
task = <Task pending name='Task-2' coro=<task_fun() running at ~\Work\book\book_python\lang\async3.py:3>>
start task1
finish task1
ret = None
  • 다음 코드는 3개의 task를 동시 실행하는 예시
  • task3, task2, task1 순서로 스케줄링
  • 처음 나타나는 await task3 라인에서 main 함수는 멈추고 스케줄링된 task가 실행 시작
  • task3 실행중 sleep을 만나면 task3는 일시 정지하고 task2 또는 task1이 실행 (순서는 미정)
  • 만약 task2이 실행되었다고 가정하고 실행중 sleep을 만나면 task2는 일시 정지하고 task1이 실행
  • 이 코드에서는 await task3 이 완료된 시점에서는 task1과 task2도 이미 완료된 상태이므로 await task2, await task1 이 사실상 의미가 없음
# async4.py
import asyncio

async def task_fun(x: int) -> None:
    print(f"start task{x}")
    await asyncio.sleep(x)
    print(f"finish task{x}")

async def main():

    task3 = asyncio.create_task(task_fun(3))
    task2 = asyncio.create_task(task_fun(2))
    task1 = asyncio.create_task(task_fun(1))

    ret = await task3
    ret = await task2
    ret = await task1

if __name__ == "__main__":
    asyncio.run(main())
$ python async4.py
start task3
start task2
start task1
finish task1
finish task2
finish task3
  • 실제로 await task2, await task1 라인을 없애도 정상 동작함
# async5.py
import asyncio

async def task_fun(x: int) -> None:
    print(f"start task{x}")
    await asyncio.sleep(x)
    print(f"finish task{x}")

async def main():

    task3 = asyncio.create_task(task_fun(3))
    task2 = asyncio.create_task(task_fun(2))
    task1 = asyncio.create_task(task_fun(1))

    ret = await task3

if __name__ == "__main__":
    asyncio.run(main())
$ python async5.py
start task3
start task2
start task1
finish task1
finish task2
finish task3
  • 하지만 다른 라인을 없애고 await task1 라인만 남기면 task2, task3이 끝날때까지 기다리지 않고 종료
# async6.py
import asyncio

async def task_fun(x: int) -> None:
    print(f"start task{x}")
    await asyncio.sleep(x)
    print(f"finish task{x}")

async def main():

    task3 = asyncio.create_task(task_fun(3))
    task2 = asyncio.create_task(task_fun(2))
    task1 = asyncio.create_task(task_fun(1))

    ret = await task1

if __name__ == "__main__":
    asyncio.run(main())
$ python async6.py
start task3
start task2
start task1
finish task1