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_futureprograma 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.gatherywaitenvuelva las corrutinas dadas como tareas usandoensure_future(vea las fuentes aquí y aquí ). Por lo tanto, no tiene sentido usarlo deensure_futureantemano, y no tiene nada que ver con obtener los resultados o no.ensure_futuretiene unloopargumento, por lo que no hay razón para usarloop.create_taskoverensure_future. Yrun_in_executorno funcionará con corrutinas, se debe usar un semáforo en su lugar.create_taskoverensure_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.awaitrecupera valores de corrutinas, es decir, "llama" a la corrutinaeusure_future/create_taskprograme 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,
awaiten una corrutinaCreamos dos corrutinas,
awaituna, y usamoscreate_taskpara 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
awaitella, 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_taskllamada, creat_task programará la rutina para que se ejecute en la siguiente iteración. Entonces, en el resultado,create taskse 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_tasken realidad llamaasyncio.tasks.Task(), que llamaráloop.call_soon. Yloop.call_soonpondrá 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_futurey deasyncio.gatherhecho llamarloop.create_taskdirecta o indirectamente.También tenga en cuenta en los documentos :
fuente
await task2podrí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 task2y aún así task2 eventualmente se ejecutará. En ex2, el comportamiento será idéntico, yaawait task2que 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 ces equivalente aawait create_task(c)?Del BDFL [2013]
Tareas
Con esto en mente,
ensure_futuretiene 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_taskes la forma preferida de asegurar un futuro .Nota: Cambié "rendimiento de" en las diapositivas de Guido a "esperar" aquí la modernidad.
fuente