Diferença entre corotina e futuro / tarefa no Python 3.5?

Digamos que temos uma função fictícia:

async def foo(arg):
    result = await some_remote_call(arg)
    return result.upper()

Qual é a diferença entre:

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

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

E:

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: O exemplo retorna um resultado, mas esse não é o foco da pergunta. Quando o valor de retorno importa, usegather() ao invés dewait().

Independentemente do valor de retorno, estou procurando clareza sobreensure_future(). wait(coros) ewait(futures) ambos executam as corotinas; portanto, quando e por que uma corotina deve ser envolvida emensure_future?

Basicamente, qual é o caminho certo (tm) para executar um monte de operações sem bloqueio usando o Python 3.5async?

Para crédito extra, e se eu quiser agrupar as chamadas? Por exemplo, preciso ligarsome_remote_call(...) 1000 vezes, mas não quero esmagar o servidor web / banco de dados / etc com 1000 conexões simultâneas. Isso é possível com um pool de threads ou processos, mas existe uma maneira de fazer isso comasyncio?

questionAnswers(4)

yourAnswerToTheQuestion