asyncio.ensure_future vs. BaseEventLoop.create_task vs. prosty coroutine?

100

Widziałem kilka podstawowych samouczków Pythona 3.5 o asyncio wykonujących tę samą operację w różnych smakach. W tym kodzie:

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)

Wszystkie trzy powyższe warianty, które definiują futureszmienną, dają ten sam wynik; jedyną różnicą, jaką widzę, jest to, że w trzecim wariancie wykonanie nie działa (co w większości przypadków nie powinno mieć znaczenia). Czy jest jakaś inna różnica? Czy są przypadki, w których nie mogę po prostu użyć najprostszego wariantu (zwykła lista programów)?

crusaderky
źródło

Odpowiedzi:

119

Aktualne informacje:

Począwszy od Pythona 3.7 dodano w tym celu asyncio.create_task(coro)funkcję wysokiego poziomu .

Powinieneś użyć go zamiast innych sposobów tworzenia zadań z coroutimes. Jeśli jednak chcesz utworzyć zadanie z dowolnego oczekiwanego, powinieneś użyć asyncio.ensure_future(obj).


Stare informacje:

ensure_future vs create_task

ensure_futureJest to sposób, aby utworzyć Taskz coroutine. Tworzy zadania na różne sposoby w oparciu o argumenty (w tym użycie create_taskdla programów i obiektów przyszłościowych).

create_taskjest abstrakcyjną metodą AbstractEventLoop. Różne pętle zdarzeń mogą implementować tę funkcję na różne sposoby.

Do ensure_futuretworzenia zadań należy używać . Będziesz potrzebny create_tasktylko wtedy, gdy zamierzasz zaimplementować własny typ pętli zdarzeń.

Aktualizacja:

@ bj0 wskazał na odpowiedź Guido na ten temat:

Chodzi o ensure_future()to, że jeśli masz coś, co może być albo coroutine, albo a Future(ta ostatnia zawiera a, Taskponieważ jest to podklasa Future), i chcesz mieć możliwość wywołania metody, która jest zdefiniowana tylko w Future(prawdopodobnie jedynej przydatnym przykładem jest cancel()). Kiedy już jest Future(lub Task) to nic nie robi; kiedy jest to program, zawija go w plik Task.

Jeśli wiesz, że masz program i chcesz, aby został on zaplanowany, odpowiednim interfejsem API jest create_task(). Jedynym momentem, w którym powinieneś wywoływać, ensure_future()jest dostarczanie API (jak większość własnych interfejsów API asyncio), które akceptuje coroutine lub a Futurei musisz zrobić z nim coś, co wymaga posiadania pliku Future.

i później:

W końcu nadal uważam, że ensure_future()jest to odpowiednio mało znana nazwa rzadko potrzebnego elementu funkcjonalności. Tworząc zadanie z programu, należy skorzystać z odpowiednio nazwanego loop.create_task(). Może powinien być do tego alias asyncio.create_task()?

To dla mnie zaskakujące. Moją główną motywacją do używania przez ensure_futurecały czas było to, że jest to funkcja wyższego poziomu w porównaniu do elementu członkowskiego pętli create_task(dyskusja zawiera kilka pomysłów, takich jak dodawanie asyncio.spawnlub asyncio.create_task).

Mogę również wskazać, że moim zdaniem dość wygodne jest użycie uniwersalnej funkcji, która obsługuje dowolne, Awaitablea nie tylko programy.

Jednak odpowiedź Guido jest jasna: „Tworząc zadanie z poziomu programu, należy użyć odpowiednio nazwanego loop.create_task()

Kiedy programy powinny być opakowane w zadania?

Wrap coroutine in a Task - to sposób na uruchomienie tego programu „w tle”. Oto przykład:

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

Wynik:

first
long_operation started
second
long_operation finished

Możesz zastąpić asyncio.ensure_future(long_operation())go tylko await long_operation()po to, aby poczuć różnicę.

Michaił Gierasimow
źródło
3
Według Guido powinieneś użyć, create_taskjeśli naprawdę potrzebujesz obiektu zadania, którego normalnie nie potrzebujesz: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0
@ bj0 dziękuję za ten link. Zaktualizowałem odpowiedź dodając informacje z tej dyskusji.
Michaił Gierasimow
czy ensure_futureautomatycznie dodaje utworzone Taskdo głównej pętli zdarzeń?
AlQuemist
@AlQuemist każdy program, przyszłość lub zadanie, które tworzysz, jest automatycznie przypisywany do jakiejś pętli zdarzeń, gdzie zostanie wykonany później. Domyślnie jest to bieżąca pętla zdarzeń dla bieżącego wątku, ale można określić inną pętlę zdarzeń za pomocą loopargumentu słowa kluczowego ( patrz podpis zapewnia_przyszłość ).
Michaił Gierasimow,
2
@laycat potrzebujemy awaitwewnątrz, msg()aby zwrócić kontrolę do pętli zdarzeń przy drugim wywołaniu. Pętla zdarzeń po odebraniu sterowania będzie mogła się rozpocząć long_operation(). Ma to na celu zademonstrowanie, w jaki sposób ensure_futureprogram uruchamia się równolegle z bieżącym przepływem wykonywania.
Michaił Gierasimow
47

create_task()

  • akceptuje programy,
  • zwraca Task,
  • jest wywoływana w kontekście pętli.

ensure_future()

  • akceptuje Futures, coroutines, obiekty oczekiwane,
  • zwraca Task (lub Future, jeśli Future minęło).
  • jeśli dany argument jest procesem, którego używa create_task,
  • obiekt pętli może zostać przekazany.

Jak widać, create_task jest bardziej szczegółowe.


async funkcjonować bez funkcji create_task lub secure_future

Prosta asyncfunkcja wywołująca zwraca coroutine

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

A ponieważ gatherpod maską zapewnia ( ensure_future), że argumenty są przyszłością, jawnie ensure_futurejest zbędne.

Podobne pytanie Jaka jest różnica między loop.create_task, asyncio.async / sure_future i Task?

kwarunek
źródło
13

Uwaga: dotyczy tylko Pythona 3.7 (w przypadku Pythona 3.5 zajrzyj do wcześniejszej odpowiedzi ).

Z oficjalnych dokumentów:

asyncio.create_task(dodane w Pythonie 3.7) jest preferowanym sposobem tworzenia nowych zadań zamiast ensure_future().


Szczegół:

Tak więc teraz, w Pythonie 3.7 i nowszych, istnieją dwie funkcje opakowujące najwyższego poziomu (podobne, ale różne):

Cóż, ostatecznie obie te funkcje opakowujące pomogą Ci wywołać BaseEventLoop.create_task. Jedyna różnica to ensure_futureakceptacjaawaitable obiektu i pomoc w przekształceniu go w przyszłość. Możesz także podać własny event_loopparametr w ensure_future. W zależności od tego, czy potrzebujesz tych możliwości, czy nie, możesz po prostu wybrać, którego opakowania chcesz użyć.

Yeo
źródło
Myślę, że jest jeszcze jedna różnica, która nie została udokumentowana: jeśli spróbujesz wywołać asyncio.create_task przed uruchomieniem pętli, będziesz miał problem, ponieważ asyncio.create_task oczekuje działającej pętli. W tym przypadku możesz jednak użyć asyncio.ensure_future, ponieważ pętla bieżąca nie jest wymagana.
coelhudo
4

na przykład wszystkie trzy typy są wykonywane asynchronicznie. jedyną różnicą jest to, że w trzecim przykładzie wstępnie wygenerowałeś wszystkie 10 programów i przesłałeś je razem do pętli. więc tylko ostatni daje wyniki losowo.

ospider
źródło