asyncio(2)

노재원·2023년 1월 29일
0

python

목록 보기
3/3

전체 코드

코루틴 정의하기

일반 함수 정의에서 맨 앞에 async 키워드만 붙이면 코루틴을 정의할 수 있다.

async def func():
	print("hello")
    await asyncio.sleep(1)

코루틴 실행하기

  1. await로 실행

    async def func(delay, msg):
      await asyncio.sleep(delay)
      print(msg)
        
    async def main():
        await func(1, "hello")
        await func(2, "world")
        
    if __name__ == "__main__":
        start = time.time()
        asyncio.run(main())
        end = time.time()
        print(str(round(end - start)) + " sec")
    # hello
    # world
    # 3 sec

    코루틴을 그냥 await로 실행하면 기대했던 비동기가 아닌 동기적으로 동작한다. 이전 글에서 await의 2번째 역할을 "실행권 받기를 기대하며 eventLoop에 실행권을 넘긴다" 라고 했다. 따라서 await func()을 호출한 시점에서 실행권은 이미 eventloop로 넘어가서 1초동안 멈춰있게 되고, 코루틴이 끝나서 main으로 다시 실행권이 넘어오게 되면 그때서야 await func2()가 실행된다.

    단 await는 코루틴 함수 내부에서만 사용할 수 있다.

  2. 비동기 실행
    코루틴을 이용하여 비동기적으로 실행시키려면 task로 실행시키면 된다. 예제부터 살펴보자.

    async def func(delay, msg):
      await asyncio.sleep(delay)
      print(msg)
    
    async def main():
      task = asyncio.create_task(func(1, "hello"))
      task2 = asyncio.create_task(func(2, "world"))
      await task
      await task2
    
    if __name__ == "__main__":
        start = time.time()
        asyncio.run(main())
        end = time.time()
        print(str(round(end - start)) + " sec")
    # hello
    # world
    # 2 sec
    

    코루틴을 테스크로 감싸서 실행시키면, 코루틴은 곧바로 실행되도록 예약된다. 그렇게 때문에 create_task 시점에서 이미 코루틴은 실행되고 있다고 볼 수 있으며 반환된 task 객체로 실행중인 코루틴을 취소할 수 있다. 테스크를 await 하면 이벤트 루프에 등록하거나 하는 등의 과정 없이, 그냥 단순히 코루틴이 끝날 때까지 기다리기만 한다.

EventLoop

사실 High Level 사용자들은 eventloop를 직접 코드로 건들일은 없다. 보통 asyncio.run() 같은 함수를 사용하면 eventloop를 알아서 생성하고 사용한다. 하지만 작업하다 보면 한번쯤은 eventloop를 빈번히 사용해서 알아두면 좋다.

  • eventloop 생성 or 받아오기
    get_event_loop 함수를 사용하면, 이미 실행중인 eventloop가 있을 경우 해당 eventloop를 반환하고, 없으면 새로 만들어서 반환한다. 아래는 공식 홈페이지에 eventloop를 사용하는 예제코드를 가져왔다. 5초 동안의 현재시간을 출력하는 예제이며, 하나하나 살펴보자.

    import asyncio
    import datetime
    
    def display_date(end_time, loop):
        print(datetime.datetime.now())
        if (loop.time() + 1.0) < end_time:
            loop.call_later(1, display_date, end_time, loop)
        else:
            loop.stop()
    
    loop = asyncio.get_event_loop()
    end_time = loop.time() + 5.0
    
    loop.call_soon(display_date, end_time, loop)
    
    try:
        loop.run_forever()
    finally:
        loop.close()
    1. get_event_loop함수로 eventloop를 생성
    2. loop.time()은 eventloop 내부 시계의 현재 시간을 float형으로 반환해주는 함수로, 끝나는 시간을 지금으로부터 5초 뒤로 설정
    3. loop.call_soon(...)함수는, 매개변수로 넘긴 callback 함수가 eventloop의 다음 iteration에 실행되도록 예약하는 함수이다. 따라서 display_date라는 callback함수가 eventloop에 등록되어, 다음 iteration에 자동으로 실행된다.
    4. display_date callback함수는 끝나는 시간과 eventloop를 매개변수로 받아서, eventloop의 현재시간이 end_time을 지나지 않았다면 if문을 실행하고, 지났다면 eventloop를 멈춘다.
    5. if문 안의loop.call_later(time, callback, args)함수는, time초 뒤에 callback함수가 실행되도록 eventloop에 등록하는 함수이다. 위에 코드에서는 1초 뒤에 자기자신을 실행시키는 것이다. 매번 같은 eventloop를 넘겨주기 때문에 정확히 5초 뒤에 else문으로 인해 eventloop가 종료된다.
    6. loop.run_forever()함수는 loop.stop()함수나 외부에 종료 시그널을 받기 전까지 eventloop를 무한히 실행시킨다. 5초 뒤에 loop.stop()함수가 호출되므로 eventloop가 멈추고, finally문으로 넘어가 eventloop를 닫으면서 종료된다.

      loop.stop()함수가 불렸을 때 eventloop가 실행중인 callback들을 전부 끝낸 다음에 eventloop가 멈춘다.

응용하자면, eventloop를 매개변수로 매번 넘겨주지 않고 callback함수 안에서 get_event_loop로 eventloop를 사용해도 같은 결과를 얻을 수 있다. 이건 각자 직접 수정해서 돌려보길 바란다.

0개의 댓글