Python

Asyncio - async, await

foxlee 2022. 7. 2. 17:40

* 최근 asyncio 를 api 테스트를 하면서 많이 사용하게 되었다. 사실상 지금은 부하테스트용도로 만들긴 했지만 실제 1초에 수만건 이상의 요청을 테스트하기 위한 다른 툴을 나중에 적용해보면 좋을듯 싶다.

* 오늘 이 글의 작성 목적은 코드로 구현 하는 것보다는 Asyncio에 대한 히스토리, 개념 등에 조금 더 알고 이해한 내용에 대해 기록하는 것이다.

 

* 파이썬 공식문서와 여러 블로그들을 읽었는데 블로그는 대부분의 내용이 겹쳤다. 파이썬 공식문서가 오히려 더 다양하게 설명되어 있고, 필요한 부분만 적을 예정이다.

 

Asyncio - https://docs.python.org/3/library/asyncio.html

  • A library to write concurrent code using the async/await syntax.
  • Used as a foundation for multiple Python asynchronous frameworks that provide high-performance network and web-servers, database connection libraries, distributed task queues, etc.
  • Often, a perfect fit for IO-bound and high-level structured network code.

 

이벤트 루프 - https://docs.python.org/3/library/asyncio-eventloop.html

  • The event loop is the core of every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses.
  • Application developers should typically use the high-level asyncio functions, such as asyncio.run(), and should rarely need to reference the loop object or call its methods. This section is intended mostly for authors of lower-level code, libraries, and frameworks, who need finer control over the event loop behavior.
    • asyncio.get_event_loop()
      • Get the current event loop. If there is no current event loop set in the current OS thread, the OS thread is main, and set_event_loop() has not yet been called, asyncio will create a new event loop and set it as the current one.
      • Because this function has rather complex behavior (especially when custom event loop policies are in use), using the get_running_loop() function is preferred to get_event_loop() in coroutines and callbacks.
      • Consider also using the asyncio.run() function instead of using lower level functions to manually create and close an event loop.
 

코루틴 - https://docs.python.org/ko/3.10/library/asyncio-task.html#id2

  • Coroutines declared with the async/await syntax is the preferred way of writing asyncio applications.
  • 코루틴을 동시에 실행하기 위해서는 태스크를 생성해야함 - asyncio.create_tast()
>>> import asyncio

>>> async def main():
...     print('hello')
...     await asyncio.sleep(1)  
...     print('world')

>>> asyncio.run(main())
hello
world
# 단순히 코루틴을 실행하는 것은 의미가 없음, 실행을 예약하는 것이 아님 -> 비동기로 작동하지 않음 1초후 world 출력


import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

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

# The asyncio.create_task() function to run coroutines concurrently as asyncio Tasks.
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

Awaitable - await 표현식에 사용가능한 대상

  • 코루틴, 태스크, 퓨처으로 3가지
  • 코루틴 함수 = def 앞에 async 가 붙은 함수
  • 코루틴 객체: 코루틴 함수를 호출하여 반환되는 객체
# 태스크 : Tasks are used to schedule coroutines concurrently, 
# When a coroutine is wrapped into a Task with functions like asyncio.create_task() 
# the coroutine is automatically scheduled to run soon
import asyncio

async def nested():
    return 42

async def main():
    # Schedule nested() to run soon concurrently
    # with "main()".
    task = asyncio.create_task(nested())

    # "task" can now be used to cancel "nested()", or
    # can simply be awaited to wait until it is complete:
    await task

asyncio.run(main())
  • Future 객체
    • Future is a special low-level awaitable object that represents an eventual result of an asynchronous operation. When a Future object is awaited it means that the coroutine will wait until the Future is resolved in some other place. Future objects in asyncio are needed to allow callback-based code to be used with async/await. Normally there is no need to create Future objects at the application level code.
  • urlopen이나 response.read 같은 함수(메서드)는 결과가 나올 때까지 코드 실행이 중단(block)되는데 이런 함수들을 블로킹 I/O(blocking I/O) 함수라고 부른다. 특히 네이티브 코루틴 안에서 블로킹 I/O 함수를 실행하려면 이벤트 루프의 run_in_executor 함수를 사용하여 다른 스레드에서 병렬로 실행시켜야 합니다. run_in_executor의 첫 번째 인수는 executor인데 함수를 실행시켜줄 스레드 풀 또는 프로세스 풀이며 여기서는 None을 넣어서 기본 스레드 풀을 사용합니다. 그리고 두 번째 인수에는 실행할 함수를 넣고 세 번째 인수부터는 실행할 함수에 들어갈 인수를 차례대로 넣어준다.

 

 

https://chronosa.tistory.com/entry/python-asyncio%EB%A1%9C-boto3-%EC%8B%A4%ED%96%89%EC%8B%9C%ED%82%A4%EA%B8%B0-%EB%B6%80%EC%A0%9C-asyncio%EB%9E%80