Python 3.5에서 코 루틴과 미래 / 작업의 차이점은 무엇입니까?


99

더미 함수가 있다고 가정 해 보겠습니다.

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를 사용하여 비 차단 작업을 실행하는 올바른 방법 (tm)은 async무엇입니까?

추가 크레딧을 위해 통화를 일괄 처리하려면 어떻게해야합니까? 예를 들어 some_remote_call(...)1000 번 전화를 걸어야하지만 1000 개의 동시 연결로 웹 서버 / 데이터베이스 등을 부수고 싶지는 않습니다. 이것은 스레드 또는 프로세스 풀을 사용하여 수행 할 수 있지만이 작업을 수행하는 방법이 asyncio있습니까?

답변:


94

코 루틴은 값을 산출하고 외부에서 값을받을 수있는 생성기 함수입니다. 코 루틴 사용의 이점은 함수 실행을 일시 중지하고 나중에 다시 시작할 수 있다는 것입니다. 네트워크 작업의 경우 응답을 기다리는 동안 함수 실행을 일시 중지하는 것이 좋습니다. 시간을 사용하여 다른 기능을 실행할 수 있습니다.

미래는 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 Taskdone코 루틴이 값을 반환하거나 예외를 발생 시키거나 취소되는 경우입니다. 이러한 사건을 확인하는 방법이 있습니다.

도움이 될만한 다음 주제에 대한 블로그 게시물을 작성했습니다.

물론 공식 매뉴얼 ( https://docs.python.org/3/library/asyncio.html) 에서 자세한 내용을 확인할 수 있습니다.


3
좀 더 명확하게 질문을 업데이트했습니다. 코 루틴의 결과가 필요하지 않은 경우에도 여전히 사용해야 ensure_future()합니까? 결과가 필요하면 그냥 사용할 수 run_until_complete(gather(coros))없습니까?
knite

1
ensure_future코 루틴이 이벤트 루프에서 실행되도록 예약합니다. 그래서 나는 예라고 말할 것입니다. 하지만 물론 다른 기능 / 방법을 사용하여 코 루틴을 예약 할 수도 있습니다. 예, 사용할 수 있습니다 gather(). 그러나 gather는 모든 응답이 수집 될 때까지 기다립니다.
masnun

5
@AbuAshrafMasnun @knite를 사용 gather하고 wait실제로 주어진 코 루틴을 작업으로 래핑합니다 ensure_future( 여기여기 에서 소스 참조 ). 따라서 ensure_future사전 에 사용 하는 것은 의미가 없으며 결과를 얻는 것과 관련이 없습니다.
Vincent

8
또한 @knite @AbuAshrafMasnun, ensure_futureloop인수를 이렇게 사용하는 이유가없는 loop.create_task이상은 ensure_future. 그리고 run_in_executor코 루틴 작동하지 않습니다하는 세마포어 대신 사용해야합니다.
Vincent

2
@vincent create_taskover 사용하는 이유가 있습니다 . 문서를ensure_future 참조하십시오 . 인용문create_task() (added in Python 3.7) is the preferable way for spawning new tasks.
masi jul.

24

간단한 대답

  • 코 루틴 함수 ( 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직접 또는 간접적으로.

또한 문서 에서 참고하십시오 .

콜백은 등록 된 순서대로 호출됩니다. 각 콜백은 정확히 한 번 호출됩니다.


1
깨끗한 설명에 감사드립니다! 말하자면, 그것은 꽤 끔찍한 디자인입니다. 높은 수준의 API는 API를 지나치게 복잡하게 만드는 낮은 수준의 추상화를 유출하고 있습니다.
Boris Burkov 2019

1
잘 디자인 된
curio

좋은 설명! 그 await task2호출 의 효과는 분명해질 것이라고 생각합니다 . 두 예제에서 loop.create_task () 호출은 이벤트 루프에서 task2를 예약합니다. 따라서 두 exs에서 삭제할 수 await task2있으며 여전히 task2는 결국 실행됩니다. ex2에서는 await task2이미 완료된 작업 (두 번째 실행되지 않음)을 예약하는 것으로 믿기 때문에 동작이 동일 할 것이며, ex1에서는 main이 완료 될 때까지 task2가 실행되지 않기 때문에 동작이 약간 다를 것입니다. 차이점을 보려면 print("end of main")ex1의 메인 끝에 추가하십시오
Andrew

10

https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346에 링크 된 Vincent의 댓글 wait()은 코 루틴 을 감싸는 것을 보여줍니다 .ensure_future() !

즉, 우리는 미래가 필요하며 코 루틴은 조용히 그들로 변형 될 것입니다.

코 루틴 / 미래를 배치하는 방법에 대한 명확한 설명을 찾으면이 답변을 업데이트 할 것입니다.


코 루틴 객체의 c경우 다음 await c과 같 await create_task(c)습니까?
Alexey

3

BDFL에서 [2013]

과제

  • Future에 싸인 코 루틴
  • Task 클래스는 Future 클래스의 하위 클래스입니다.
  • 그래서 그것은 너무 기다림 과 함께 작동합니다 !

  • 베어 코 루틴과 어떻게 다른가요?
  • 기다리지 않고 진행할 수 있습니다.
    • 다른 것을 기다리는 한, 즉
      • 기다립니다 [something_else]

이를 염두에두고 ensure_future미래의 결과가 (당신이 무언가를 기다리는 한) 기다리고 있는지 여부에 관계없이 계산되므로 Task를 만드는 이름으로 의미가 있습니다 . 이렇게하면 다른 작업을 기다리는 동안 이벤트 루프가 작업을 완료 할 수 있습니다. Python 3.7 에서는 미래를 보장하는create_task 것이 선호되는 방법 입니다.

참고 : 나는 Guido의 슬라이드에서 "yield from"을 여기서 "await"로 변경했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.