asyncio.ensure_future vs. BaseEventLoop.create_task vs. corrotina simples?

96

Eu vi vários tutoriais básicos do Python 3.5 sobre asyncio fazendo a mesma operação em vários sabores. Neste 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)

Todas as três variantes acima que definem a futuresvariável alcançam o mesmo resultado; a única diferença que posso ver é que com a terceira variante a execução está fora de ordem (o que não deve importar na maioria dos casos). Existe alguma outra diferença? Existem casos em que não posso usar apenas a variante mais simples (lista simples de corrotinas)?

cruzado
fonte

Respostas:

116

Informação real:

A partir do Python 3.7 asyncio.create_task(coro), a função de alto nível foi adicionada para esse propósito.

Você deve usá-lo em vez de outras maneiras de criar tarefas de co-rotinas. No entanto, se você precisar criar uma tarefa arbitrária, deve usar asyncio.ensure_future(obj).


Informação antiga:

ensure_future vs create_task

ensure_futureé um método para criar Taska partir de coroutine. Ele cria tarefas de maneiras diferentes com base em argumentos (incluindo o uso decreate_task para co-rotinas e objetos semelhantes ao futuro).

create_task é um método abstrato de AbstractEventLoop . Loops de eventos diferentes podem implementar essa função de maneiras diferentes.

Você deve usar ensure_futurepara criar tarefas. Você precisarácreate_task apenas se for implementar seu próprio tipo de loop de evento.

Upd:

@ bj0 apontou para a resposta de Guido sobre este assunto:

O ponto de ensure_future()é se você tem algo que poderia ser uma co-rotina ou um Future(o último inclui um Taskporque é uma subclasse de Future), e você deseja ser capaz de chamar um método nele que está definido apenas em Future(provavelmente sobre o único exemplo útil sendo cancel()). Quando já é um Future(ou Task), isso não faz nada; quando é uma co-rotina, envolve -a em a Task.

Se você sabe que tem uma co-rotina e deseja que ela seja agendada, a API correta a ser usada é create_task(). O único momento em que você deve chamar ensure_future()é quando está fornecendo uma API (como a maioria das próprias APIs do asyncio) que aceita uma co-rotina ou a Futuree você precisa fazer algo que exija que você tenha uma Future.

e depois:

No final, eu ainda acredito que ensure_future() é um nome apropriadamente obscuro para uma funcionalidade raramente necessária. Ao criar uma tarefa a partir de uma co-rotina, você deve usar o nome apropriado loop.create_task(). Talvez devesse haver um apelido para isso asyncio.create_task()?

É surpreendente para mim. Minha principal motivação para usar o tempo ensure_futuretodo foi que é uma função de nível superior em comparação com o membro do loopcreate_task (a discussão contém algumas idéias como adicionar asyncio.spawnouasyncio.create_task ).

Também posso apontar que, na minha opinião, é muito conveniente usar uma função universal que pode lidar com qualquer Awaitable vez de apenas co-rotinas.

No entanto, a resposta de Guido é clara: "Ao criar uma tarefa a partir de uma co-rotina, você deve usar o nome apropriadoloop.create_task() "

Quando as corrotinas devem ser envolvidas nas tarefas?

Envolva a co-rotina em uma Tarefa - é uma maneira de iniciar esta co-rotina "em segundo plano". Aqui está um exemplo:

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

Resultado:

first
long_operation started
second
long_operation finished

Você pode substituir asyncio.ensure_future(long_operation())apenas await long_operation()para sentir a diferença.

Mikhail Gerasimov
fonte
3
De acordo com Guido, você deve usar create_taskse realmente precisar de um objeto de tarefa, que normalmente não deveria: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0
@ bj0 obrigado por este link. Eu atualizei a resposta adicionando informações desta discussão.
Mikhail Gerasimov
não ensure_futureadiciona automaticamente o criado Taskao ciclo de eventos principal?
AlQuemist
@AlQuemist cada corrotina, futuro ou tarefa que você cria é automaticamente ligada a algum loop de evento, onde será executada mais tarde. Por padrão, é o loop de evento atual para o thread atual, mas você pode especificar outro loop de evento usando o loopargumento de palavra-chave ( consulte a assinatura garantir_future ).
Mikhail Gerasimov
2
@laycat, precisamos awaitdentro msg()para retornar o controle ao loop de eventos na segunda chamada. O loop de eventos, uma vez que receba o controle, poderá ser iniciado long_operation(). Foi feito para demonstrar como a ensure_futureco-rotina começa a ser executada simultaneamente com o fluxo de execução atual.
Mikhail Gerasimov,
45

create_task()

  • aceita corrotinas,
  • retorna Tarefa,
  • ele é invocado no contexto do loop.

ensure_future()

  • aceita Futuros, corrotinas, objetos aguardáveis,
  • retorna Task (ou Future se Future for aprovado).
  • se o arg fornecido é uma co-rotina, ele usa create_task ,
  • objeto de loop pode ser passado.

Como você pode ver, o create_task é mais específico.


async função sem criar_task ou garantir_future

asyncFunção de invocação simples retorna co-rotina

>>> async def doit(i):
...     await asyncio.sleep(3)
...     return i
>>> doit(4)   
<coroutine object doit at 0x7f91e8e80ba0>

E uma vez que os gatherbastidores garantem ( ensure_future) que args são futuros, explicitamenteensure_future é redundante.

Pergunta semelhante Qual é a diferença entre loop.create_task, asyncio.async / verify_future e Task?

Kwarunek
fonte
13

Observação: válido apenas para Python 3.7 (para Python 3.5, consulte a resposta anterior ).

Dos documentos oficiais:

asyncio.create_task(adicionado no Python 3.7) é a maneira preferível de gerar novas tarefas em vez de ensure_future().


Detalhe:

Portanto, agora, no Python 3.7 em diante, existem 2 funções de wrapper de nível superior (semelhantes, mas diferentes):

Bem, ao final, ambas as funções do wrapper irão ajudá-lo a chamar BaseEventLoop.create_task. A única diferença é ensure_futureaceitar qualquer awaitableobjeto e ajudá-lo a convertê-lo em um Futuro. E você também pode fornecer seu próprio event_loopparâmetro em ensure_future. E dependendo se você precisa desses recursos ou não, você pode simplesmente escolher qual wrapper usar.

Yeo
fonte
Acho que há outra diferença que não está documentada: se você tentar chamar asyncio.create_task antes de executar o loop, terá um problema, pois asyncio.create_task está esperando um loop em execução. Você pode usar asyncio.ensure_future neste caso, no entanto, uma vez que um loop em execução não é um requisito.
coelhudo
4

para seu exemplo, todos os três tipos são executados de forma assíncrona. a única diferença é que, no terceiro exemplo, você pré-gerou todas as 10 corrotinas e as submeteu ao loop juntas. portanto, apenas o último fornece saída aleatoriamente.

ospider
fonte