더미 함수가 있다고 가정 해 보겠습니다.
async def foo(arg):
result = await some_remote_call(arg)
return result.upper()
차이점은 무엇입니까?
coros = []
for i in range(5):
coros.append(foo(i))
loop = get_event_loop()
loop.run_until_complete(wait(coros))
과:
from asyncio import ensure_future
futures = []
for i in range(5):
futures.append(ensure_future(foo(i)))
loop = get_event_loop()
loop.run_until_complete(wait(futures))
참고 :이 예제는 결과를 반환하지만 질문의 초점이 아닙니다. 반환 값 문제는, 사용하는 경우 gather()
대신 wait()
.
반환 값에 관계없이 ensure_future()
. wait(coros)
그리고 wait(futures)
모두는 코 루틴을 실행, 그래서 언제, 왜 코 루틴을 감싸한다 ensure_future
?
기본적으로 Python 3.5를 사용하여 비 차단 작업을 실행하는 올바른 방법 ™은 async
무엇입니까?
추가 크레딧을 위해 통화를 일괄 처리하려면 어떻게해야합니까? 예를 들어 some_remote_call(...)
1000 번 전화를 걸어야하지만 1000 개의 동시 연결로 웹 서버 / 데이터베이스 등을 부수고 싶지는 않습니다. 이것은 스레드 또는 프로세스 풀을 사용하여 수행 할 수 있지만이 작업을 수행하는 방법이 asyncio
있습니까?
답변
코 루틴은 값을 산출하고 외부에서 값을받을 수있는 생성기 함수입니다. 코 루틴 사용의 이점은 함수 실행을 일시 중지하고 나중에 다시 시작할 수 있다는 것입니다. 네트워크 작업의 경우 응답을 기다리는 동안 함수 실행을 일시 중지하는 것이 좋습니다. 시간을 사용하여 다른 기능을 실행할 수 있습니다.
미래는 Promise
자바 스크립트 의 객체 와 같습니다 . 미래에 구체화 될 가치에 대한 자리 표시 자입니다. 위에서 언급 한 경우 네트워크 I / O를 기다리는 동안 함수는 컨테이너를 제공하여 작업이 완료되면 컨테이너를 값으로 채울 것임을 약속합니다. 우리는 미래의 객체를 붙잡고 그것이 충족되면 실제 결과를 검색하기 위해 그것에 대한 메소드를 호출 할 수 있습니다.
직접 답변 : 당신은 필요하지 않습니다 ensure_future
당신은 결과가 필요하지 않은 경우. 결과가 필요하거나 예외가 발생한 경우 유용합니다.
추가 크레딧 : 최대 작업자 수를 제어하기 run_in_executor
위해 Executor
인스턴스를 선택 하고 전달합니다 .
설명 및 샘플 코드
첫 번째 예에서는 코 루틴을 사용하고 있습니다. 이 wait
함수는 많은 코 루틴을 가져 와서 함께 결합합니다. 따라서 wait()
모든 코 루틴이 소진되면 완료됩니다 (모든 값을 반환하는 완료 / 완료 됨).
loop = get_event_loop() #
loop.run_until_complete(wait(coros))
이 run_until_complete
메서드는 실행이 완료 될 때까지 루프가 살아 있는지 확인합니다. 이 경우 비동기 실행의 결과를 얻지 못하는 것을 주목하십시오.
두 번째 예제에서는 ensure_future
함수를 사용하여 코 루틴을 래핑 Task
하고 일종의 Future
. 코 루틴은를 호출 할 때 메인 이벤트 루프에서 실행되도록 예약되어 있습니다 ensure_future
. 반환 된 미래 / 작업 객체에는 아직 값이 없지만 시간이 지남에 따라 네트워크 작업이 완료되면 미래 객체가 작업 결과를 보유합니다.
from asyncio import ensure_future
futures = []
for i in range(5):
futures.append(ensure_future(foo(i)))
loop = get_event_loop()
loop.run_until_complete(wait(futures))
따라서이 예제에서는 코 루틴을 사용하는 대신 퓨처를 사용하는 것을 제외하고는 동일한 작업을 수행합니다.
asyncio / coroutines / futures를 사용하는 방법의 예를 살펴 보겠습니다.
import asyncio
async def slow_operation():
await asyncio.sleep(1)
return 'Future is done!'
def got_result(future):
print(future.result())
# We have result, so let's stop
loop.stop()
loop = asyncio.get_event_loop()
task = loop.create_task(slow_operation())
task.add_done_callback(got_result)
# We run forever
loop.run_forever()
여기에서는 객체 create_task
에 대한 메서드를 사용했습니다 loop
. ensure_future
메인 이벤트 루프에서 작업을 예약합니다. 이 방법을 사용하면 선택한 루프에서 코 루틴을 예약 할 수 있습니다.
add_done_callback
작업 객체 의 메서드를 사용하여 콜백을 추가하는 개념도 확인했습니다 .
A Task
는 done
코 루틴이 값을 반환하거나 예외를 발생 시키거나 취소되는 경우입니다. 이러한 사건을 확인하는 방법이 있습니다.
도움이 될만한 다음 주제에 대한 블로그 게시물을 작성했습니다.
- http://masnun.com/2015/11/13/python-generators-coroutines-native-coroutines-and-async-await.html
- http://masnun.com/2015/11/20/python-asyncio-future-task-and-the-event-loop.html
- http://masnun.com/2015/12/07/python-3-using-blocking-functions-or-codes-with-asyncio.html
물론 공식 매뉴얼 ( https://docs.python.org/3/library/asyncio.html) 에서 자세한 내용을 확인할 수 있습니다.
답변
간단한 대답
- 코 루틴 함수 (
async def
)를 호출 해도 실행되지 않습니다. 제너레이터 함수가 제너레이터 객체를 반환하는 것처럼 코 루틴 객체를 반환합니다. await
코 루틴에서 값을 검색합니다. 즉, 코 루틴을 “호출”합니다.eusure_future/create_task
코 루틴이 다음 반복시 이벤트 루프에서 실행되도록 예약합니다 (데몬 스레드처럼 완료 될 때까지 기다리지는 않지만).
일부 코드 예제
먼저 몇 가지 용어를 정리해 보겠습니다.
- 코 루틴 함수, 당신이하는 것
async def
; - 코 루틴 객체, 코 루틴 함수를 “호출”할 때 얻은 것;
- task, 이벤트 루프에서 실행되는 코 루틴 객체를 감싸는 객체.
await
코 루틴에 대한 사례 1
await
하나 는 두 개의 코 루틴을 만들고 create_task
다른 하나를 실행하는 데 사용 합니다.
import asyncio
import time
# coroutine function
async def p(word):
print(f'{time.time()} - {word}')
async def main():
loop = asyncio.get_event_loop()
coro = p('await') # coroutine
task2 = loop.create_task(p('create_task')) # <- runs in next iteration
await coro # <-- run directly
await task2
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
결과를 얻을 수 있습니다.
1539486251.7055213 - await
1539486251.7055705 - create_task
설명:
task1은 직접 실행되었고 task2는 다음 반복에서 실행되었습니다.
사례 2, 이벤트 루프에 대한 제어 양보
주 함수를 바꾸면 다른 결과를 볼 수 있습니다.
async def main():
loop = asyncio.get_event_loop()
coro = p('await')
task2 = loop.create_task(p('create_task')) # scheduled to next iteration
await asyncio.sleep(1) # loop got control, and runs task2
await coro # run coro
await task2
결과를 얻을 수 있습니다.
-> % python coro.py
1539486378.5244057 - create_task
1539486379.5252144 - await # note the delay
설명:
를 호출 할 때 asyncio.sleep(1)
컨트롤이 이벤트 루프로 다시 양보되고 루프가 실행할 작업을 확인한 다음에서 만든 작업을 실행합니다 create_task
.
먼저 코 루틴 함수를 호출하지만 호출하지 않습니다 await
. 따라서 코 루틴 하나를 생성하고 실행하지 않습니다. 그런 다음 코 루틴 함수를 다시 호출하고 호출로 래핑합니다 create_task
. creat_task는 실제로 코 루틴이 다음 반복에서 실행되도록 예약합니다. 그래서, 결과에서, create task
이전에 실행됩니다 await
.
사실, 여기서 중요한 점은 루프에 대한 제어권을 되 돌리는 asyncio.sleep(0)
것입니다. 동일한 결과를 보는 데 사용할 수 있습니다.
후드
loop.create_task
실제로 호출 asyncio.tasks.Task()
호출하는 loop.call_soon
. 그리고 loop.call_soon
작업을 loop._ready
. 루프를 반복 할 때마다 loop._ready의 모든 콜백을 확인하고 실행합니다.
asyncio.wait
, asyncio.ensure_future
그리고 asyncio.gather
실제로 전화를 loop.create_task
직접 또는 간접적으로.
또한 문서 에서 참고하십시오 .
콜백은 등록 된 순서대로 호출됩니다. 각 콜백은 정확히 한 번 호출됩니다.
답변
https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346에 링크 된 Vincent의 댓글 wait()
은 코 루틴 을 감싸는 것을 보여줍니다 .ensure_future()
!
즉, 우리는 미래가 필요하며 코 루틴은 조용히 그들로 변형 될 것입니다.
코 루틴 / 미래를 배치하는 방법에 대한 명확한 설명을 찾으면이 답변을 업데이트 할 것입니다.
답변
과제
- Future에 싸인 코 루틴
- Task 클래스는 Future 클래스의 하위 클래스입니다.
- 그래서 그것은 너무 기다림 과 함께 작동합니다 !
- 베어 코 루틴과 어떻게 다른가요?
- 기다리지 않고 진행할 수 있습니다.
- 다른 것을 기다리는 한, 즉
- 기다립니다 [something_else]
- 다른 것을 기다리는 한, 즉
이를 염두에두고 ensure_future
미래의 결과가 (당신이 무언가를 기다리는 한) 기다리고 있는지 여부에 관계없이 계산되므로 Task를 만드는 이름으로 의미가 있습니다 . 이렇게하면 다른 작업을 기다리는 동안 이벤트 루프가 작업을 완료 할 수 있습니다. Python 3.7 에서는 미래를 보장하는create_task
것이 선호되는 방법 입니다.
참고 : 나는 Guido의 슬라이드에서 “yield from”을 여기서 “await”로 변경했습니다.