Powiedzmy, że mamy funkcję fikcyjną:
async def foo(arg):
result = await some_remote_call(arg)
return result.upper()
Jaka jest różnica pomiędzy:
import asyncio
coros = []
for i in range(5):
coros.append(foo(i))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coros))
I:
import asyncio
futures = []
for i in range(5):
futures.append(asyncio.ensure_future(foo(i)))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))
Uwaga : przykład zwraca wynik, ale nie jest to główny temat pytania. Gdy wartość zwracana ma znaczenie, użyj gather()
zamiast wait()
.
Niezależnie od wartości zwracanej, szukam jasności ensure_future()
. wait(coros)
i wait(futures)
oba obsługują programy, więc kiedy i dlaczego powinien być zawinięty w coroutine ensure_future
?
Zasadniczo, jaki jest właściwy sposób (tm), aby wykonać kilka operacji nieblokujących przy użyciu języka Python 3.5 async
?
Aby uzyskać dodatkowe środki, co jeśli chcę grupować połączenia? Na przykład muszę dzwonić some_remote_call(...)
1000 razy, ale nie chcę niszczyć serwera WWW / bazy danych / itp. Przy 1000 jednoczesnych połączeniach. Można to zrobić za pomocą wątku lub puli procesów, ale czy istnieje sposób, aby to zrobić asyncio
?
Aktualizacja 2020 (Python 3.7+) : Nie używaj tych fragmentów. Zamiast tego użyj:
import asyncio
async def do_something_async():
tasks = []
for i in range(5):
tasks.append(asyncio.create_task(foo(i)))
await asyncio.gather(*tasks)
def do_something():
asyncio.run(do_something_async)
Rozważ także użycie Trio , solidnej alternatywy innej firmy dla asyncio.
źródło
ensure_future()
? A jeśli potrzebuję wyniku, czy nie mogę po prostu użyćrun_until_complete(gather(coros))
?ensure_future
planuje wykonanie programu w pętli zdarzeń. Więc powiedziałbym, że tak, to jest wymagane. Ale oczywiście możesz zaplanować programy za pomocą innych funkcji / metod. Tak, możesz użyćgather()
- ale zbieraj będzie czekać, aż wszystkie odpowiedzi zostaną zebrane.gather
iwait
faktycznie zawiń podane programy jako zadania przy użyciuensure_future
(zobacz źródła tutaj i tutaj ). Więc nie ma sensu używać goensure_future
wcześniej i nie ma to nic wspólnego z uzyskaniem wyników lub nie.ensure_future
maloop
argumentu, więc nie ma powodu, aby korzystać zloop.create_task
ponadensure_future
. Irun_in_executor
nie będzie działać z coroutines, zamiast tego należy użyć semafora .create_task
overensure_future
, zobacz dokumentację . Cytatcreate_task() (added in Python 3.7) is the preferable way for spawning new tasks.
Prosta odpowiedź
async def
) NIE powoduje jej uruchomienia. Zwraca dodatkowe obiekty, tak jak funkcja generatora zwraca obiekty generatora.await
pobiera wartości z programów, tj. „wywołuje” programeusure_future/create_task
zaplanuj, aby program uruchomił się w pętli zdarzeń przy następnej iteracji (chociaż nie czeka na zakończenie, jak wątek demona).Kilka przykładów kodu
Najpierw wyjaśnijmy kilka terminów:
async def
;Przypadek 1,
await
w programieTworzymy dwa programy,
await
jeden i używamy gocreate_task
do uruchamiania drugiego.otrzymasz wynik:
Wyjaśnić:
zadanie1 zostało wykonane bezpośrednio, a zadanie2 zostało wykonane w kolejnej iteracji.
Przypadek 2, oddanie kontroli pętli zdarzeń
Jeśli zastąpimy główną funkcję, zobaczymy inny wynik:
otrzymasz wynik:
Wyjaśnić:
Podczas wywoływania
asyncio.sleep(1)
formant został przekazany z powrotem do pętli zdarzeń, a pętla sprawdza zadania do uruchomienia, a następnie uruchamia zadanie utworzone przezcreate_task
.Zwróć uwagę, że najpierw wywołujemy funkcję coroutine, ale nie
await
ją, więc utworzyliśmy tylko jeden coroutine i nie uruchamialiśmy go. Następnie ponownie wywołujemy funkcję coroutine i zawijamy ją wcreate_task
wywołanie, creat_task faktycznie zaplanuje działanie programu w następnej iteracji. W rezultaciecreate task
jest wykonywany wcześniejawait
.Właściwie chodzi o przywrócenie kontroli nad pętlą, której można by użyć,
asyncio.sleep(0)
aby zobaczyć ten sam wynik.Pod maską
loop.create_task
faktycznie dzwoniasyncio.tasks.Task()
, który zadzwoniloop.call_soon
. Iloop.call_soon
włączy zadanieloop._ready
. Podczas każdej iteracji pętli sprawdza wszystkie wywołania zwrotne w loop._ready i uruchamia je.asyncio.wait
,asyncio.ensure_future
aasyncio.gather
właściwie zadzwońloop.create_task
bezpośrednio lub pośrednio.Zwróć również uwagę w dokumentach :
źródło
await task2
wezwania mógłby zostać wyjaśniony. W obu przykładach wywołanie loop.create_task () jest tym, co planuje zadanie2 w pętli zdarzeń. Więc w obu ex można usunąć,await task2
a task2 w końcu zostanie uruchomiony. W ex2 zachowanie będzie identyczne, ponieważawait task2
uważam, że jest to po prostu planowanie już ukończonego zadania (które nie zostanie uruchomione po raz drugi), podczas gdy w ex1 zachowanie będzie nieco inne, ponieważ zadanie2 nie zostanie wykonane do zakończenia main. Aby zobaczyć różnicę, dodajprint("end of main")
na końcu głównej części ex1Komentarz Vincenta pod linkiem https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346 , który pokazuje, że
wait()
wszystko to jestensure_future()
dla Ciebie!Innymi słowy, potrzebujemy przyszłości, a programy po cichu zostaną w nie przekształcone.
Zaktualizuję tę odpowiedź, gdy znajdę ostateczne wyjaśnienie, jak grupować coroutines / futures.
źródło
c
,await c
jest odpowiednikiemawait create_task(c)
?Z BDFL [2013]
Zadania
Mając to na uwadze,
ensure_future
ma sens jako nazwa do tworzenia Zadania, ponieważ wynik Przyszłości zostanie obliczony bez względu na to, czy czekasz na to (o ile czekasz na coś). Dzięki temu pętla zdarzeń może zakończyć Twoje zadanie, gdy czekasz na inne rzeczy. Zauważ, że w Pythonie 3.7create_task
jest preferowanym sposobem zapewnienia przyszłości .Uwaga: zmieniłem „ustępowanie” na slajdach Guido na „czekaj” tutaj na nowoczesność.
źródło