3.1 Moduł asyncio
Swoich wątków dla asynchronicznych podzadań już dawno nikt nie tworzy. W sumie, można je tworzyć, ale takie działania są uważane za zbyt niskopoziomowe i stosowane jedynie przez twórców frameworków. I to tylko wtedy, gdy się bez nich nie obejdzie.
Obecnie na topie jest programowanie asynchroniczne, operatory async/await
i korutyny z zadaniami (tasks). Ale po kolei…
Trochę historii
Początkowo w Pythonie do rozwiązywania zadań asynchronicznego programowania używano korutyn, opartych na generatorach. Potem, w Pythonie 3.4, pojawił się moduł asyncio
(czasem jego nazwę zapisują jako async IO
), w którym zrealizowano mechanizmy asynchronicznego programowania. W Pythonie 3.5 pojawiła się konstrukcja async/await
.
A teraz trochę wprowadzenia. Najpierw opowiem krótko o tych wszystkich rzeczach, potem szczegółowiej, a następnie jeszcze bardziej szczegółowo. Nie da się inaczej, gdyż prawie wszystkie one działają w pakiecie i nie da się szczegółowo wytłumaczyć działania jednej funkcji bez odniesienia się do innych.
Moduł asyncio
Moduł asyncio
jest przeznaczony do pisania programów asynchronicznych, zapewniając możliwość równoległego wykonywania zadań. Wspiera operacje asynchroniczne wejścia-wyjścia, timery, sockety, wykonywanie korutyn i wielowątkowość, działając w jednym lub kilku pętlach zdarzeń.
Korutyny (Coroutines)
Korutyny to funkcje asynchroniczne, definiowane za pomocą słowa kluczowego async def
. Korutyny pozwalają zawieszać swoje wykonywanie za pomocą słowa kluczowego await
, co pozwala innym korutynom wykonywać się w tym czasie
.
import asyncio
# Definicja funkcji asynchronicznej (korutyny)
async def main():
print('Hello ...')
# Zawieszamy wykonywanie na 1 sekundę
await asyncio.sleep(1)
print('... World!')
# Uruchomienie funkcji asynchronicznej main() w pętli zdarzeń
asyncio.run(main())
Pętla zdarzeń (Event Loop)
Pętla zdarzeń kontroluje wykonywanie korutyn, zadań i innych operacji asynchronicznych. Wywołanie asyncio.run()
uruchamia pętlę zdarzeń i wykonuje korutynę aż do jej zakończenia.
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# Pobranie bieżącej pętli zdarzeń
loop = asyncio.get_event_loop()
# Uruchomienie korutyny aż do jej zakończenia
loop.run_until_complete(main())
# Zamknięcie pętli zdarzeń po zakończeniu wszystkich zadań
loop.close()
Zadania (Tasks)
Zadania pozwalają uruchamiać korutyny równolegle. Tworzone są za pomocą asyncio.create_task()
lub asyncio.ensure_future()
.
import asyncio
# Definicja korutyny, która będzie wykonana z opóźnieniem
async def say_after(delay, what):
# Zawieszamy wykonywanie na podany czas
await asyncio.sleep(delay)
print(what)
# Główna korutyna
async def main():
# Tworzymy zadania dla równoległego wykonywania korutyn
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# Czekamy na zakończenie obu zadań
await task1
await task2
# Uruchomienie głównej korutyny
asyncio.run(main())
Futures (Futures)
Obiekty Future
reprezentują wyniki operacji asynchronicznych, które będą dostępne w przyszłości. Na przykład, są one używane do oczekiwania na zakończenie asynchronicznego zadania.
import asyncio
# Definicja korutyny, która symuluje długie zadanie
async def long_running_task():
print('Task started')
# Zawieszamy wykonywanie na 3 sekundy
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# Główna korutyna
async def main():
# Tworzymy future do oczekiwania na zakończenie zadania
future = asyncio.ensure_future(long_running_task())
# Czekamy na zakończenie zadania i pobieramy wynik
result = await future
print(f'Task result: {result}')
# Uruchomienie głównej korutyny
asyncio.run(main())
3.2 Asynchroniczna funkcja — async def
Asynchroniczna funkcja deklaruje się tak samo jak zwykła, tylko przed słowem kluczowym def
trzeba napisać słowo async
.
async def NazwaFunkcji(parametry):
kod funkcji
Asynchroniczna funkcja jest deklarowana jak zwykła, wywoływana jak zwykła, ale wynik zwraca inny. Jeśli wywołać asynchroniczną funkcję, to nie zwróci ona wyniku, lecz specjalny obiekt — korutynę.
Można to nawet sprawdzić:
import asyncio
async def main():
print("Hello World")
# Wywołanie funkcji asynchronicznej, która zwraca korutynę
result = main()
# Sprawdzenie typu wyniku
print(type(result)) # <class 'coroutine'>
Co się dzieje? Kiedy oznaczasz funkcję słowem async, to właściwie dodajesz do niej dekorator, który robi mniej więcej to:
def async_decorator(func):
# Tworzymy obiekt Task
task = Task()
# Przekazujemy do niego naszą funkcję func, żeby ją wykonał
task.target = func
# Dodajemy obiekt task do kolejki zadań — Event Loop
eventloop.add_task(task)
# Zwracamy obiekt task
return task
Twój kod staje się podobny do:
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
Sens tej analogii jest następujący:
Kiedy wywołujesz funkcję asynchroniczną, tworzony jest specjalny obiekt Task
, który będzie wykonywał twoją funkcję, ale kiedykolwiek w przyszłości. Może za 0.0001 sekundy, a może dopiero za 10.
Ten obiekt task
jest ci od razu zwracany jako wynik wywołania twojej funkcji asynchronicznej. To jest właśnie korutyna. Twoja funkcja asynchroniczna mogła jeszcze w ogóle nie zaczęła się wykonywać, a obiekt task
(korutyna) już masz.
Po co ci ten task
(korutyna)? Niewiele możesz z nim zrobić, ale można zrobić 3 rzeczy:
- Poczekać, aż funkcja asynchroniczna się wykona.
- Poczekać, aż funkcja asynchroniczna zakończy się i uzyskać z korutiny wynik wykonania funkcji.
- Poczekać, aż wykona się 10 (dowolna liczba) funkcji asynchronicznych.
Jak to zrobić, opowiem poniżej.
3.3 Operator await
Większość działań z korutyną zaczyna się od „poczekaj na wykonanie funkcji asynchronicznej". Dlatego dla tej czynności mamy specjalny operator await
.
Musisz po prostu napisać go przed korutyną:
await korutyna
Lub od razu przed wywołaniem funkcji asynchronicznej:
await funkcja_asynchroniczna(argumenty)
Kiedy Python napotyka w kodzie operator await
, to wstrzymuje wykonywanie bieżącej funkcji i czeka, aż korutyna się wykona — aż zakończy się funkcja asynchroniczna, do której odwołuje się korutyna.
Ważne!
Operator await
stosuje się tylko wewnątrz funkcji asynchronicznej do wstrzymania wykonywania do momentu, aż zakończy się inna korutyna lub operacja asynchroniczna.
Robi się to po to, by uprościć proces przełączania między wywołaniami funkcji asynchronicznych. Takie wywołanie await
— to de facto deklaracja „będziemy tutaj czekać nie wiadomo jak długo — zajmijcie się wykonywaniem innych funkcji asynchronicznych".
Przykład:
import asyncio
# Definicja funkcji asynchronicznej
async def async_print(text):
print(text)
# Główna funkcja asynchroniczna
async def main():
# Używamy await, by poczekać na wykonanie funkcji asynchronicznej
await async_print("Hello World")
# Uruchomienie głównej pętli zdarzeń i wykonanie korutyny main()
asyncio.run(main()) #uruchamia funkcję asynchroniczną
W rzeczywistości operator await
działa jeszcze sprytniej — również zwraca wynik wykonania funkcji asynchronicznej, dla której został wywołany.
Przykład:
import asyncio
# Definicja funkcji asynchronicznej, która dodaje dwie liczby
async def async_add(a, b):
return a + b
# Główna funkcja asynchroniczna
async def main():
# Używamy await, by uzyskać wynik wykonania async_add
sum = await async_add(100, 200)
print(sum)
# Uruchomienie głównej pętli zdarzeń i wykonanie korutyny main()
asyncio.run(main()) #uruchamia funkcję asynchroniczną
A więc, podsumowując, operator await
:
- Wstrzymuje bieżącą funkcję asynchroniczną do momentu, aż zakończy się inna korutyna lub operacja asynchroniczna.
- Zwraca wynik wykonania operacji asynchronicznej lub korutyny.
- Można stosować go tylko wewnątrz funkcji asynchronicznej.
GO TO FULL VERSION