asyncio.ensure_future vs.BaseEventLoop.create_task vs.coroutine simple?

96

He visto varios tutoriales básicos de Python 3.5 sobre asyncio haciendo la misma operación en varios sabores. En este código:

import asyncio  

async def doit(i):
    print("Start %d" % i)
    await asyncio.sleep(3)
    print("End %d" % i)
    return i

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    #futures = [asyncio.ensure_future(doit(i), loop=loop) for i in range(10)]
    #futures = [loop.create_task(doit(i)) for i in range(10)]
    futures = [doit(i) for i in range(10)]
    result = loop.run_until_complete(asyncio.gather(*futures))
    print(result)

Las tres variantes anteriores que definen la futuresvariable logran el mismo resultado; la única diferencia que puedo ver es que con la tercera variante la ejecución está fuera de orden (lo que no debería importar en la mayoría de los casos). ¿Hay alguna otra diferencia? ¿Hay casos en los que no puedo usar la variante más simple (lista simple de corrutinas)?

cruzado
fuente

Respuestas:

116

Información real:

A partir de Python 3.7 asyncio.create_task(coro), se agregó la función de alto nivel para este propósito.

Debería utilizarlo en lugar de otras formas de crear tareas a partir de tiempos de ejecución. Sin embargo, si necesita crear una tarea a partir de espera arbitraria, debe usar asyncio.ensure_future(obj).


Información antigua:

ensure_future vs create_task

ensure_futurees un método para crear Taskde coroutine. Crea tareas de diferentes maneras basadas en argumentos (incluido el uso de create_taskpara corrutinas y objetos de tipo futuro).

create_taskes un método abstracto de AbstractEventLoop. Diferentes bucles de eventos pueden implementar esta función de diferentes maneras.

Debería utilizarlo ensure_futurepara crear tareas. Solo lo necesitará create_tasksi va a implementar su propio tipo de bucle de eventos.

Upd:

@ bj0 señaló la respuesta de Guido sobre este tema:

El punto ensure_future()es si tiene algo que podría ser una corrutina o un Future(este último incluye un Taskporque es una subclase de Future), y desea poder llamar a un método en él que solo está definido en Future(probablemente el único siendo un ejemplo útil cancel()). Cuando ya es un Future(o Task) esto no hace nada; cuando es una corrutina, lo envuelve en a Task.

Si sabe que tiene una corrutina y desea que se programe, la API correcta que debe usar es create_task(). El único momento en el que debería llamar ensure_future()es cuando está proporcionando una API (como la mayoría de las API propias de asyncio) que acepta una corrutina o una Futurey necesita hacer algo que requiera que tenga una Future.

y después:

Al final, sigo creyendo que ensure_future()es un nombre apropiadamente oscuro para una pieza de funcionalidad que rara vez se necesita. Al crear una tarea a partir de una corrutina, debe utilizar el nombre apropiado loop.create_task(). ¿Quizás debería haber un alias para eso asyncio.create_task()?

Me sorprende. Mi principal motivación para usar ensure_futuretodo el tiempo fue que es una función de nivel superior en comparación con el miembro del bucle create_task(la discusión contiene algunas ideas como agregar asyncio.spawno asyncio.create_task).

También puedo señalar que, en mi opinión, es bastante conveniente usar la función universal que puede manejar cualquiera en Awaitablelugar de solo corrutinas.

Sin embargo, la respuesta de Guido es clara: "Al crear una tarea a partir de una corrutina, debe utilizar el nombre apropiado loop.create_task()"

¿Cuándo se deben incluir las corrutinas en tareas?

Envolver la corrutina en una tarea: es una forma de iniciar esta corrutina "en segundo plano". He aquí un ejemplo:

import asyncio


async def msg(text):
    await asyncio.sleep(0.1)
    print(text)


async def long_operation():
    print('long_operation started')
    await asyncio.sleep(3)
    print('long_operation finished')


async def main():
    await msg('first')

    # Now you want to start long_operation, but you don't want to wait it finised:
    # long_operation should be started, but second msg should be printed immediately.
    # Create task to do so:
    task = asyncio.ensure_future(long_operation())

    await msg('second')

    # Now, when you want, you can await task finised:
    await task


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Salida:

first
long_operation started
second
long_operation finished

Puede reemplazarlo asyncio.ensure_future(long_operation())con solo await long_operation()sentir la diferencia.

Mikhail Gerasimov
fuente
3
Según Guido, debe usar create_tasksi realmente necesita un objeto de tarea, que normalmente no debería necesitar: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0
@ bj0 gracias por este enlace. Actualicé la respuesta agregando información de esta discusión.
Mikhail Gerasimov
¿ ensure_futureAgrega automáticamente lo creado Taskal bucle de eventos principal?
AlQuemist
@AlQuemist cada rutina, futuro o tarea que crea se vincula automáticamente a algún bucle de eventos, donde se ejecutará más tarde. De forma predeterminada, es el bucle de eventos actual para el hilo actual, pero puede especificar otro bucle de eventos usando el loopargumento de palabra clave ( consulte la firma de asegurar el futuro ).
Mikhail Gerasimov
2
@laycat necesitamos awaitdentro msg()para devolver el control al bucle de eventos en la segunda llamada. El bucle de eventos una vez que reciba el control podrá comenzar long_operation(). Se hizo para demostrar cómo ensure_futurecomienza una rutina para ejecutarse simultáneamente con el flujo de ejecución actual.
Mikhail Gerasimov
45

create_task()

  • acepta corrutinas,
  • devuelve Task,
  • se invoca en el contexto del bucle.

ensure_future()

  • acepta Futuros, corrutinas, objetos en espera,
  • devuelve Task (o Future si Future pasó).
  • si el argumento dado es una corrutina que usa create_task,
  • se puede pasar el objeto de bucle.

Como puede ver, create_task es más específico.


async función sin create_task o asegurar_futuro

La asyncfunción de invocación simple devuelve una rutina

>>> async def doit(i):
...     await asyncio.sleep(3)
...     return i
>>> doit(4)   
<coroutine object doit at 0x7f91e8e80ba0>

Y dado que lo gatheroculto asegura ( ensure_future) que los argumentos son futuros, explícitamente ensure_futurees redundante.

Pregunta similar ¿ Cuál es la diferencia entre loop.create_task, asyncio.async / secure_future y Task?

kwarunek
fuente
13

Nota: Solo es válido para Python 3.7 (para Python 3.5, consulte la respuesta anterior ).

De los documentos oficiales:

asyncio.create_task(agregado en Python 3.7) es la forma preferible de generar nuevas tareas en lugar de ensure_future().


Detalle:

Así que ahora, en Python 3.7 en adelante, hay 2 funciones contenedoras de nivel superior (similares pero diferentes):

Bueno, en definitiva ambas funciones contenedoras te ayudarán a llamar BaseEventLoop.create_task. La única diferencia es ensure_futureaceptar cualquier awaitableobjeto y ayudarte a convertirlo en un futuro. Y también puede proporcionar su propio event_loopparámetro en ensure_future. Y dependiendo de si necesita esa capacidad o no, simplemente puede elegir qué envoltorio usar.

Yeo
fuente
Creo que hay otra diferencia que no está documentada: si intentas llamar a asyncio.create_task antes de ejecutar el ciclo, tendrás un problema ya que asyncio.create_task espera un ciclo en ejecución. Sin embargo, puede usar asyncio.ensure_future en este caso, ya que un bucle en ejecución no es un requisito.
coelhudo
4

para su ejemplo, los tres tipos se ejecutan de forma asincrónica. la única diferencia es que, en el tercer ejemplo, generaste previamente las 10 corrutinas y las enviaste al ciclo juntas. por lo que solo el último da salida al azar.

ospider
fuente