asyncio.ensure_future contro BaseEventLoop.create_task contro semplice coroutine?


97

Ho visto diversi tutorial di base su Python 3.5 su asyncio che eseguono la stessa operazione in vari gusti. In questo codice:

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)

Tutte e tre le varianti sopra che definiscono la futuresvariabile ottengono lo stesso risultato; l'unica differenza che posso vedere è che con la terza variante l'esecuzione è fuori servizio (il che non dovrebbe avere importanza nella maggior parte dei casi). C'è qualche altra differenza? Ci sono casi in cui non posso usare solo la variante più semplice (semplice elenco di coroutine)?

Risposte:


117

Informazioni effettive:

A questo scopo, a partire da Python 3.7 asyncio.create_task(coro), è stata aggiunta la funzione di alto livello .

Dovresti usarlo invece in altri modi per creare attività da coroutimes. Tuttavia, se è necessario creare un'attività da attesa arbitraria, è necessario utilizzare asyncio.ensure_future(obj).


Vecchie informazioni:

ensure_future vs create_task

ensure_futureè un metodo per creare Taskda coroutine. Crea attività in modi diversi in base all'argomento (incluso l'uso di create_taskper coroutine e oggetti simili al futuro).

create_taskè un metodo astratto di AbstractEventLoop. Cicli di eventi diversi possono implementare questa funzione in modi diversi.

Dovresti usare ensure_futureper creare attività. Ne avrai bisogno create_tasksolo se intendi implementare il tuo tipo di loop di eventi.

Aggiornare:

@ bj0 ha indicato la risposta di Guido su questo argomento:

Il punto ensure_future()è che se hai qualcosa che potrebbe essere una coroutine o a Future(quest'ultimo include a Taskperché è una sottoclasse di Future), e vuoi essere in grado di chiamare un metodo su di esso che è definito solo su Future(probabilmente circa l'unico essere utile esempio cancel()). Quando è già un Future(o Task) questo non fa nulla; quando è una coroutine la avvolge in una Task.

Se sai di avere una coroutine e vuoi che sia pianificata, l'API corretta da usare è create_task(). L'unico momento in cui dovresti chiamare ensure_future()è quando fornisci un'API (come la maggior parte delle API di asyncio) che accetta una coroutine o a Futuree devi fare qualcosa che richiede di avere un file Future.

e più tardi:

Alla fine credo ancora che ensure_future()sia un nome adeguatamente oscuro per una parte di funzionalità raramente necessaria. Quando crei un'attività da una coroutine dovresti usare il nome appropriato loop.create_task(). Forse dovrebbe esserci un alias per questo asyncio.create_task()?

È sorprendente per me. La mia motivazione principale da utilizzare è ensure_futurestata che si tratta di una funzione di livello superiore rispetto al membro del ciclo create_task(la discussione contiene alcune idee come l'aggiunta di asyncio.spawno asyncio.create_task).

Posso anche sottolineare che a mio parere è abbastanza conveniente usare la funzione universale che può gestire qualsiasi Awaitablepiuttosto che solo le coroutine.

Tuttavia, la risposta di Guido è chiara: "Quando crei un'attività da una coroutine dovresti usare il nome appropriato loop.create_task()"

Quando le coroutine dovrebbero essere inserite nelle attività?

Avvolgere la coroutine in un'attività: è un modo per avviare questa coroutine "in background". Ecco un esempio:

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

Produzione:

first
long_operation started
second
long_operation finished

Puoi sostituire asyncio.ensure_future(long_operation())con solo await long_operation()per sentire la differenza.


3
Secondo Guido, dovresti usare create_taskse hai davvero bisogno di un oggetto attività, che normalmente non dovresti aver bisogno: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0

@ bj0 grazie per questo collegamento. Ho aggiornato la risposta aggiungendo informazioni da questa discussione.
Mikhail Gerasimov

non ensure_futureaggiunge automaticamente il creato Taskper il ciclo degli eventi principale?
AlQuemist

@AlQuemist ogni coroutine, future o attività che crei viene automaticamente associato a un ciclo di eventi, dove verrà eseguito in seguito. Per impostazione predefinita è il ciclo di eventi corrente per il thread corrente, ma è possibile specificare un altro ciclo di eventi utilizzando l' loopargomento parola chiave ( vedere la firma sure_future ).
Mikhail Gerasimov

2
@laycat abbiamo bisogno awaitall'interno msg()di restituire il controllo al ciclo di eventi in seconda convocazione. Il ciclo di eventi una volta ricevuto il controllo sarà in grado di iniziare long_operation(). È stato creato per dimostrare come ensure_futureavviare la coroutine per essere eseguita contemporaneamente al flusso di esecuzione corrente.
Mikhail Gerasimov

45

create_task()

  • accetta coroutine,
  • restituisce Task,
  • viene richiamato nel contesto del ciclo.

ensure_future()

  • accetta Futures, coroutine, oggetti attendibili,
  • restituisce Task (o Future se Future è passato).
  • se l'argomento dato è una coroutine che usa create_task,
  • l'oggetto loop può essere passato.

Come puoi vedere, create_task è più specifico.


async funzione senza create_task o assicurare_future

La semplice asyncfunzione di invocazione restituisce coroutine

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

E poiché il gathersotto il cofano assicura ( ensure_future) che gli argomenti sono futures, ensure_futureè esplicitamente ridondante.

Domanda simile Qual è la differenza tra loop.create_task, asyncio.async / sure_future e Task?


13

Nota: valido solo per Python 3.7 (per Python 3.5 fare riferimento alla risposta precedente ).

Dai documenti ufficiali:

asyncio.create_task(aggiunto in Python 3.7) è il modo preferibile per generare nuove attività invece di ensure_future().


Dettaglio:

Quindi ora, in Python 3.7 in poi, ci sono 2 funzioni wrapper di primo livello (simili ma diverse):

Bene, praticamente entrambe queste funzioni wrapper ti aiuteranno a chiamare BaseEventLoop.create_task. L'unica differenza è ensure_futureaccettare qualsiasi awaitableoggetto e aiutarti a convertirlo in un futuro. E puoi anche fornire il tuo event_loopparametro in ensure_future. E a seconda che tu abbia bisogno di queste capacità o meno, puoi semplicemente scegliere quale wrapper usare.


Penso che ci sia un'altra differenza che non è documentata: se provi a chiamare asyncio.create_task prima di eseguire il ciclo, avrai un problema poiché asyncio.create_task si aspetta un ciclo in esecuzione. Puoi usare asyncio.ensure_future in questo caso, tuttavia, poiché un ciclo in esecuzione non è un requisito.
coelhudo

4

per il tuo esempio, tutti e tre i tipi vengono eseguiti in modo asincrono. l'unica differenza è che, nel terzo esempio, hai pre-generato tutte e 10 le coroutine e le hai inviate insieme al ciclo. quindi solo l'ultimo fornisce output in modo casuale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.