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.
- asyncio.get_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 객체
- A 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을 넣어서 기본 스레드 풀을 사용합니다. 그리고 두 번째 인수에는 실행할 함수를 넣고 세 번째 인수부터는 실행할 함수에 들어갈 인수를 차례대로 넣어준다.