¿Diferencia entre corutina y futuro / tarea en Python 3.5?

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:

coros = []
for i in range(5):
    coros.append(foo(i))

loop = get_event_loop()
loop.run_until_complete(wait(coros))

Y:

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))

Nota: El ejemplo devuelve un resultado, pero este no es el foco de la pregunta. Cuando el valor de retorno es importante, usegather() en lugar dewait().

Independientemente del valor de retorno, estoy buscando claridad sobreensure_future(). wait(coros) ywait(futures) ambos ejecutan las corutinas, entonces, ¿cuándo y por qué se debe envolver una corutina?ensure_future?

Básicamente, ¿cuál es la forma correcta (tm) de ejecutar un montón de operaciones sin bloqueo utilizando Python 3.5?async?

Para obtener crédito adicional, ¿qué sucede si deseo agrupar las llamadas? Por ejemplo, necesito llamarsome_remote_call(...) 1000 veces, pero no quiero aplastar el servidor web / base de datos / etc. con 1000 conexiones simultáneas. Esto se puede hacer con un subproceso o grupo de procesos, pero hay una manera de hacerlo conasyncio?

Respuestas a la pregunta(4)

Su respuesta a la pregunta