python - run - asyncio.ensure_future vs. BaseEventLoop.create_task vs. simple coroutine?
python async api (4)
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 variable de
futures
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 corutinas)?
Nota: Solo 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 deensure_future()
.
Detalle:
Entonces, en Python 3.7 en adelante, hay 2 funciones de envoltura de nivel superior (similares pero diferentes):
-
asyncio.create_task
: que simplemente llama aevent_loop.create_task(coro)
directamente. ( ver código fuente ) -
ensure_future()
que también llama a
event_loop.create_task(coro)
si es de rutina o, de lo contrario, es simplemente para garantizar que el tipo de retorno sea asyncio.Future . ( ver código fuente ) De todos modos, laTask
sigue siendo unFuture
debido a su herencia de clase ( ref ).
Bueno, ambas funciones de contenedor lo ayudarán a llamar a
BaseEventLoop.create_task
.
La única diferencia es
ensure_future
que el
ensure_future
acepte cualquier objeto que
awaitable
y lo ayude a convertirlo en un futuro.
Y también puede proporcionar su propio parámetro
event_loop
en
ensure_future
.
Y dependiendo de si necesita esas capacidades o no, simplemente puede elegir qué envoltura usar.
Información actual:
A partir de Python 3.7,
se agregó la
función de alto nivel
asyncio.create_task(coro)
para este propósito.
En su lugar, debe usar otras formas de crear tareas a partir de tiempos de ejecución.
Sin embargo, si necesita crear una tarea de espera arbitraria, debe usar
asyncio.ensure_future(obj)
.
Información antigua:
ensure_future
vs
create_task
ensure_future
es un método para crear
Task
desde la
coroutine
.
Crea tareas de diferentes maneras basadas en argumentos (incluido el uso de
create_task
para corutinas y objetos similares al futuro).
create_task
es un método abstracto de
AbstractEventLoop
.
Los diferentes bucles de eventos pueden implementar esta función de diferentes maneras.
Debería usar
ensure_future
para crear tareas.
Necesitará
create_task
solo si va a implementar su propio tipo de bucle de eventos.
Upd:
@ bj0 señaló la respuesta de Guido sobre este tema:
El punto de
ensure_future()
es si tiene algo que podría ser una rutina o unFuture
(este último incluye unaTask
porque es una subclase deFuture
), y desea poder llamar a un método que solo está definido enFuture
(probablemente el único ejemplo útil seacancel()
). Cuando ya es unFuture
(oTask
), esto no hace nada; cuando es una rutina, la envuelve en unaTask
.Si sabe que tiene una rutina y desea que se programe, la API correcta para usar es
create_task()
. El único momento en que debería llamar aensure_future()
es cuando proporciona una API (como la mayoría de las API de asyncio) que acepta una rutina o unFuture
y necesita hacer algo que requiera que tenga unFuture
.
y después:
Al final, todavía creo que
ensure_future()
es un nombre apropiadamente oscuro para una pieza de funcionalidad que rara vez se necesita. Al crear una tarea desde una rutina, debe usar elloop.create_task()
apropiadamente nombrado. Tal vez debería haber un alias para eseasyncio.create_task()
?
Es sorprendente para mi.
Mi principal motivación para usar
ensure_future
todo el tiempo fue que es una función de nivel superior en comparación con el miembro
create_task
del bucle (la discusión
contains
algunas ideas como agregar
asyncio.spawn
o
asyncio.create_task
).
También puedo señalar que, en mi opinión, es bastante conveniente usar una función universal que pueda manejar cualquier
Awaitable
lugar de solo corutinas.
Sin embargo, la respuesta de Guido es clara:
"Al crear una tarea a partir de una rutina, debe usar el
loop.create_task()
".
¿Cuándo las corutinas deben envolverse en tareas?
Ajustar la rutina en una tarea: es una forma de comenzar esta rutina "en segundo plano". Aquí hay 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 reemplazar
asyncio.ensure_future(long_operation())
con solo
await long_operation()
para sentir la diferencia.
create_task()
- acepta corutinas,
- devuelve Tarea,
- Se invoca en el contexto del bucle.
ensure_future()
- acepta futuros, corutinas, objetos esperables,
- devuelve Tarea (o Futuro si el Futuro pasó).
-
si el argumento dado es una rutina, usa
create_task
, - Se puede pasar el objeto de bucle.
Como puede ver, create_task es más específico.
función
async
sin create_task o allow_future
La función
async
invocación simple devuelve la rutina
>>> async def doit(i):
... await asyncio.sleep(3)
... return i
>>> doit(4)
<coroutine object doit at 0x7f91e8e80ba0>
Y dado que la
gather
bajo el capó asegura (
ensure_future
) que los
ensure_future
son futuros, explícitamente,
ensure_future
es redundante.
Pregunta similar ¿ Cuál es la diferencia entre loop.create_task, asyncio.async / allow_future y Task?
para su ejemplo, los tres tipos se ejecutan de forma asincrónica. la única diferencia es que, en el tercer ejemplo, generó previamente las 10 corutinas y las envió al ciclo juntas. así que solo el último da salida al azar.