Digamos que tenemos una función ficticia:
async def foo(arg):
result = await some_remote_call(arg)
return result.upper()
Cuál es la diferencia entre:
import asyncio
coros = []
for i in range(5):
coros.append(foo(i))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coros))
Y:
import asyncio
futures = []
for i in range(5):
futures.append(asyncio.ensure_future(foo(i)))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))
Nota : El ejemplo devuelve un resultado, pero este no es el foco de la pregunta. Cuando el valor de retorno importa, use en gather()
lugar de wait()
.
Independientemente del valor de retorno, busco claridad ensure_future()
. wait(coros)
y wait(futures)
ambos ejecutan las corrutinas, entonces, ¿cuándo y por qué debería incluirse una corrutina ensure_future
?
Básicamente, ¿cuál es la forma correcta (tm) de ejecutar un montón de operaciones sin bloqueo usando Python 3.5 async
?
Para obtener crédito adicional, ¿qué sucede si quiero agrupar las llamadas? Por ejemplo, necesito llamar some_remote_call(...)
1000 veces, pero no quiero aplastar el servidor web / base de datos / etc.con 1000 conexiones simultáneas. Esto es factible con un hilo o grupo de procesos, pero ¿hay alguna forma de hacerlo asyncio
?
Actualización 2020 (Python 3.7+) : no use estos fragmentos. En su lugar use:
import asyncio
async def do_something_async():
tasks = []
for i in range(5):
tasks.append(asyncio.create_task(foo(i)))
await asyncio.gather(*tasks)
def do_something():
asyncio.run(do_something_async)
También considere usar Trio , una alternativa sólida de terceros a asyncio.
fuente
ensure_future()
? Y si necesito el resultado, ¿no puedo usarlorun_until_complete(gather(coros))
?ensure_future
programa la corrutina que se ejecutará en el bucle de eventos. Entonces yo diría que sí, es obligatorio. Pero, por supuesto, también puede programar las corrutinas utilizando otras funciones / métodos. Sí, puede usargather()
, pero recopilar esperará hasta que se recopilen todas las respuestas.gather
ywait
envuelva las corrutinas dadas como tareas usandoensure_future
(vea las fuentes aquí y aquí ). Por lo tanto, no tiene sentido usarlo deensure_future
antemano, y no tiene nada que ver con obtener los resultados o no.ensure_future
tiene unloop
argumento, por lo que no hay razón para usarloop.create_task
overensure_future
. Yrun_in_executor
no funcionará con corrutinas, se debe usar un semáforo en su lugar.create_task
overensure_future
, vea los documentos . Citacreate_task() (added in Python 3.7) is the preferable way for spawning new tasks.
Respuesta simple
async def
) NO la ejecuta. Devuelve objetos de una corrutina, como la función generadora devuelve objetos generadores.await
recupera valores de corrutinas, es decir, "llama" a la corrutinaeusure_future/create_task
programe la corrutina para que se ejecute en el bucle de eventos en la próxima iteración (aunque sin esperar a que finalicen, como un hilo de demonio).Algunos ejemplos de código
Primero aclaremos algunos términos:
async def
;Caso 1,
await
en una corrutinaCreamos dos corrutinas,
await
una, y usamoscreate_task
para ejecutar la otra.obtendrás resultado:
Explique:
task1 se ejecutó directamente y task2 se ejecutó en la siguiente iteración.
Caso 2, ceder el control al bucle de eventos
Si reemplazamos la función principal, podemos ver un resultado diferente:
obtendrás resultado:
Explique:
Al llamar
asyncio.sleep(1)
, el control se devolvió al bucle de eventos, y el bucle verifica que las tareas se ejecuten y luego ejecuta la tarea creada porcreate_task
.Tenga en cuenta que primero invocamos la función de corrutina, pero no a
await
ella, por lo que solo creamos una sola corrutina y no la ejecutamos. Luego, volvemos a llamar a la función de rutina y la envolvemos en unacreate_task
llamada, creat_task programará la rutina para que se ejecute en la siguiente iteración. Entonces, en el resultado,create task
se ejecuta antesawait
.En realidad, el punto aquí es devolver el control al bucle, podría usar
asyncio.sleep(0)
para ver el mismo resultado.Bajo el capó
loop.create_task
en realidad llamaasyncio.tasks.Task()
, que llamaráloop.call_soon
. Yloop.call_soon
pondrá la tarea enloop._ready
. Durante cada iteración del ciclo, comprueba todas las devoluciones de llamada en loop._ready y lo ejecuta.asyncio.wait
,asyncio.ensure_future
y deasyncio.gather
hecho llamarloop.create_task
directa o indirectamente.También tenga en cuenta en los documentos :
fuente
await task2
podría aclarar el efecto de la llamada. En ambos ejemplos, la llamada loop.create_task () es lo que programa task2 en el ciclo de eventos. Entonces, en ambos exs, puede eliminar elawait task2
y aún así task2 eventualmente se ejecutará. En ex2, el comportamiento será idéntico, yaawait task2
que creo que solo está programando la tarea ya completada (que no se ejecutará por segunda vez), mientras que en ex1 el comportamiento será ligeramente diferente ya que la tarea2 no se ejecutará hasta que la principal esté completa. Para ver la diferencia, agregueprint("end of main")
al final de la página principal de ex1Un comentario de Vincent vinculado a https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346 , que muestra que te
wait()
envuelve las corrutinasensure_future()
.En otras palabras, necesitamos un futuro, y las corrutinas se transformarán silenciosamente en ellos.
Actualizaré esta respuesta cuando encuentre una explicación definitiva de cómo agrupar corutinas / futuros.
fuente
c
,await c
es equivalente aawait create_task(c)
?Del BDFL [2013]
Tareas
Con esto en mente,
ensure_future
tiene sentido como un nombre para la creación de un Grupo a que el resultado de que el futuro será computada si está o no esperan que (siempre y cuando usted espera algo). Esto permite que el ciclo de eventos complete su tarea mientras espera otras cosas. Tenga en cuenta que en Python 3.7create_task
es la forma preferida de asegurar un futuro .Nota: Cambié "rendimiento de" en las diapositivas de Guido a "esperar" aquí la modernidad.
fuente