asyncio.ensure_future vs. BaseEventLoop.create_task vs. einfache Coroutine?

96

Ich habe mehrere grundlegende Python 3.5-Tutorials zu Asyncio gesehen, die dieselbe Operation in verschiedenen Varianten ausführen. In diesem Code:

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)

Alle drei oben genannten Varianten, die die futuresVariable definieren , erzielen das gleiche Ergebnis. Der einzige Unterschied, den ich sehen kann, ist, dass bei der dritten Variante die Ausführung nicht in Ordnung ist (was in den meisten Fällen keine Rolle spielen sollte). Gibt es noch einen anderen Unterschied? Gibt es Fälle, in denen ich nicht einfach die einfachste Variante verwenden kann (einfache Liste der Coroutinen)?

Kreuzfahrer
quelle

Antworten:

116

Aktuelle Infos:

Ab Python 3.7 wurde zu diesem Zweck eine asyncio.create_task(coro)übergeordnete Funktion hinzugefügt .

Sie sollten es stattdessen auf andere Weise verwenden, um Aufgaben aus Coroutimes zu erstellen. Wenn Sie jedoch eine Aufgabe aus einer beliebigen Wartezeit erstellen müssen, sollten Sie diese verwenden asyncio.ensure_future(obj).


Alte Info:

ensure_future vs. create_task

ensure_futureist eine Methode zum Erstellen Taskaus coroutine. Es erstellt Aufgaben auf unterschiedliche Weise basierend auf Argumenten (einschließlich der Verwendung von create_taskfür Coroutinen und zukunftsähnliche Objekte).

create_taskist eine abstrakte Methode von AbstractEventLoop. Verschiedene Ereignisschleifen können diese Funktion auf unterschiedliche Weise implementieren.

Sie sollten verwenden ensure_future, um Aufgaben zu erstellen. Sie benötigen create_tasknur, wenn Sie Ihren eigenen Ereignisschleifentyp implementieren möchten.

Aktualisieren:

@ bj0 zeigte auf Guidos Antwort zu diesem Thema:

Der Punkt ensure_future()ist, wenn Sie etwas haben, das entweder eine Coroutine oder eine sein kann Future(letztere enthält eine, Taskweil das eine Unterklasse von ist Future), und Sie möchten eine Methode darauf aufrufen können, die nur für definiert ist Future(wahrscheinlich die einzige nützliches Beispiel dafür cancel()). Wenn es bereits ein Future(oder Task) ist, tut dies nichts; wenn es ein Koroutine ist es wickelt es in ein Task.

Wenn Sie wissen, dass Sie eine Coroutine haben und diese geplant werden soll, ist die richtige API zu verwenden create_task(). Das einzige Mal, wenn Sie anrufen sollten, ensure_future()ist, wenn Sie eine API bereitstellen (wie die meisten von asyncios eigenen APIs), die entweder eine Coroutine oder eine akzeptiert, Futureund Sie müssen etwas tun, für das Sie eine benötigen Future.

und später:

Letztendlich glaube ich immer noch, dass dies ensure_future()ein angemessen dunkler Name für ein selten benötigtes Stück Funktionalität ist. Wenn Sie eine Aufgabe aus einer Coroutine erstellen, sollten Sie den entsprechend benannten verwenden loop.create_task(). Vielleicht sollte es dafür einen Alias ​​geben asyncio.create_task()?

Es ist überraschend für mich. Meine Hauptmotivation ensure_futurewar es, die Funktion auf höherer Ebene im Vergleich zum Mitglied der Schleife zu verwenden create_task(die Diskussion enthält einige Ideen wie Hinzufügen asyncio.spawnoder asyncio.create_task).

Ich kann auch darauf hinweisen, dass es meiner Meinung nach ziemlich praktisch ist, eine universelle Funktion zu verwenden, die nur mit AwaitableCoroutinen umgehen kann .

Guidos Antwort ist jedoch klar: "Wenn Sie eine Aufgabe aus einer Coroutine erstellen, sollten Sie den entsprechend benannten verwenden. loop.create_task()"

Wann sollten Coroutinen in Aufgaben eingewickelt werden?

Coroutine in eine Aufgabe einwickeln - ist eine Möglichkeit, diese Coroutine "im Hintergrund" zu starten. Hier ist ein Beispiel:

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

Ausgabe:

first
long_operation started
second
long_operation finished

Sie können ersetzen, asyncio.ensure_future(long_operation())nur await long_operation()um den Unterschied zu spüren.

Mikhail Gerasimov
quelle
3
Laut Guido sollten Sie verwenden, create_taskwenn Sie wirklich ein Aufgabenobjekt benötigen, das Sie normalerweise nicht benötigen sollten: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0
@ bj0 danke für diesen Link. Ich habe die Antwort aktualisiert und Informationen aus dieser Diskussion hinzugefügt.
Mikhail Gerasimov
nicht ensure_futurefügt automatisch die erstellt , Taskum die Hauptereignisschleife?
AlQuemist
@AlQuemist Jede von Ihnen erstellte Coroutine, Zukunft oder Aufgabe wird automatisch an eine Ereignisschleife gebunden, wo sie später ausgeführt wird. Standardmäßig handelt es sich um eine aktuelle Ereignisschleife für den aktuellen Thread. Sie können jedoch eine andere Ereignisschleife mithilfe des loopSchlüsselwortarguments angeben ( siehe Signatur "sure_future" ).
Mikhail Gerasimov
2
@laycat wir müssen awaitinnerhalb msg()Kontrolle Ereignisschleife auf dem zweiten Gespräch zurückzukehren. Die Ereignisschleife kann nach dem Empfang der Steuerung gestartet werden long_operation(). Es wurde demonstriert, wie ensure_futureCoroutine gestartet wird, um gleichzeitig mit dem aktuellen Ausführungsfluss ausgeführt zu werden.
Mikhail Gerasimov
45

create_task()

  • akzeptiert Coroutinen,
  • gibt Aufgabe zurück,
  • es wird im Kontext der Schleife aufgerufen.

ensure_future()

  • akzeptiert Futures, Coroutinen, erwartete Objekte,
  • gibt Task zurück (oder Future, wenn Future bestanden hat).
  • Wenn das angegebene Argument eine Coroutine ist, die es verwendet create_task,
  • Schleifenobjekt kann übergeben werden.

Wie Sie sehen können, ist die create_task spezifischer.


async Funktion ohne create_task oder sure_future

Die einfache Aufruffunktion asyncgibt die Coroutine zurück

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

Und da das gatherunter der Haube ( ensure_future) sicherstellt, dass Argumente Futures sind, ist dies explizit ensure_futureüberflüssig.

Ähnliche Frage Was ist der Unterschied zwischen loop.create_task, asyncio.async / sure_future und Task?

kwarunek
quelle
13

Hinweis: Nur gültig für Python 3.7 (für Python 3.5 siehe die frühere Antwort ).

Aus den offiziellen Dokumenten:

asyncio.create_task(hinzugefügt in Python 3.7) ist die bevorzugte Methode zum Erstellen neuer Aufgaben anstelle von ensure_future().


Detail:

Ab Python 3.7 gibt es also zwei Wrapper-Funktionen der obersten Ebene (ähnlich, aber unterschiedlich):

Diese beiden Wrapper-Funktionen helfen Ihnen beim Aufrufen BaseEventLoop.create_task. Der einzige Unterschied besteht darin ensure_future, ein awaitableObjekt zu akzeptieren und es in eine Zukunft umzuwandeln. Außerdem können Sie Ihren eigenen event_loopParameter in angeben ensure_future. Und je nachdem, ob Sie diese Funktionen benötigen oder nicht, können Sie einfach auswählen, welcher Wrapper verwendet werden soll.

Yeo
quelle
Ich denke, es gibt einen weiteren Unterschied, der nicht dokumentiert ist: Wenn Sie versuchen, asyncio.create_task aufzurufen, bevor Sie die Schleife ausführen, tritt ein Problem auf, da asyncio.create_task eine laufende Schleife erwartet. In diesem Fall können Sie jedoch asyncio.ensure_future verwenden, da eine laufende Schleife nicht erforderlich ist.
Coelhudo
4

In Ihrem Beispiel werden alle drei Typen asynchron ausgeführt. Der einzige Unterschied besteht darin, dass Sie im dritten Beispiel alle 10 Coroutinen vorgeneriert und gemeinsam an die Schleife gesendet haben. so gibt nur der letzte zufällig Ausgabe aus.

ospider
quelle